Celo's optimism

diff: ignored:
+32904
-17621
+776
-865

This is an overview of the changes in Celo’s optimism implementation, a fork of Optimism’s optimism.

Changes are currently separated by sub-package or component. Check out the README for more details about each of these components and packages.

diff --git OP/packages/contracts-bedrock/README.md CELO/packages/contracts-bedrock/README.md index 76f9d4b96f8e1386e99d38837850d2b250fe6d21..5957a3c49a8a8bb1dfe202c0c54cda7c0206fe9e 100644 --- OP/packages/contracts-bedrock/README.md +++ CELO/packages/contracts-bedrock/README.md @@ -280,7 +280,7 @@ ```bash DEPLOYMENT_OUTFILE=deployments/artifact.json \ DEPLOY_CONFIG_PATH=<PATH_TO_MY_DEPLOY_CONFIG> \ - forge script scripts/Deploy.s.sol:Deploy \ + forge script scripts/deploy/Deploy.s.sol:Deploy \ --broadcast --private-key $PRIVATE_KEY \ --rpc-url $ETH_RPC_URL ``` @@ -339,11 +339,11 @@ ### Execution   Before deploying the contracts, you can verify the state diff produced by the deploy script using the `runWithStateDiff()` function signature which produces the outputs inside [`snapshots/state-diff/`](./snapshots/state-diff). -Run the deployment with state diffs by executing: `forge script -vvv scripts/Deploy.s.sol:Deploy --sig 'runWithStateDiff()' --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY`. +Run the deployment with state diffs by executing: `forge script -vvv scripts/deploy/Deploy.s.sol:Deploy --sig 'runWithStateDiff()' --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY`.   1. Set the env vars `ETH_RPC_URL`, `PRIVATE_KEY` and `ETHERSCAN_API_KEY` if contract verification is desired. 1. Set the `DEPLOY_CONFIG_PATH` env var to a path on the filesystem that points to a deploy config. -1. Deploy the contracts with `forge script -vvv scripts/Deploy.s.sol:Deploy --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY` +1. Deploy the contracts with `forge script -vvv scripts/deploy/Deploy.s.sol:Deploy --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY` Pass the `--verify` flag to verify the deployments automatically with Etherscan.   ### Deploying a single contract
diff --git OP/packages/contracts-bedrock/STYLE_GUIDE.md CELO/packages/contracts-bedrock/STYLE_GUIDE.md index a27d5b0e5721c7b24d91fd4d78191a00c5cb994d..cb6485c01ba2baa47ef8fa6e86223667c85c20f6 100644 --- OP/packages/contracts-bedrock/STYLE_GUIDE.md +++ CELO/packages/contracts-bedrock/STYLE_GUIDE.md @@ -110,22 +110,28 @@ 4. In the `constructor`, set any `immutable` variables and call the `initialize` function for setting mutables.   ### Versioning   -All (non-library and non-abstract) contracts MUST extend the `Semver` base contract which +All (non-library and non-abstract) contracts MUST inherit the `ISemver` interface which exposes a `version()` function that returns a semver-compliant version string.   -Contracts must have a `Semver` of `1.0.0` or greater to be production ready. Contracts -with `Semver` values less than `1.0.0` should only be used locally or on devnets. +Contracts must have a `version` of `1.0.0` or greater to be production ready.   -Additionally, contracts MUST use the following versioning scheme: +Additionally, contracts MUST use the following versioning scheme when incrementing their version:   - `patch` releases are to be used only for changes that do NOT modify contract bytecode (such as updating comments). - `minor` releases are to be used for changes that modify bytecode OR changes that expand the contract ABI provided that these changes do NOT break the existing interface. - `major` releases are to be used for changes that break the existing contract interface OR changes that modify the security model of a contract.   +The remainder of the contract versioning and release process can be found in [`VERSIONING.md](./VERSIONING.md). + #### Exceptions   We have made an exception to the `Semver` rule for the `WETH` contract to avoid making changes to a well-known, simple, and recognizable contract. + +Additionally, bumping the patch version does change the bytecode, so another exception is carved out for this. +In other words, changing comments increments the patch version, which changes bytecode. This bytecode +change implies a minor version increment is needed, but because it's just a version change, only a +patch increment should be used.   ### Dependencies
diff --git OP/packages/contracts-bedrock/VERSIONING.md CELO/packages/contracts-bedrock/VERSIONING.md new file mode 100644 index 0000000000000000000000000000000000000000..ae36a7408a3845b803acb723d2df78f7c1e63eb9 --- /dev/null +++ CELO/packages/contracts-bedrock/VERSIONING.md @@ -0,0 +1,128 @@ +# Smart Contract Versioning and Release Process + +The Smart Contract Versioning and Release Process closely follows a true [semver](https://semver.org) for both individual contracts and monorepo releases. +However, there are some changes to accommodate the unique nature of smart contract development and governance cycles. + +There are five parts to the versioning and release process: + +- [Semver Rules](#semver-rules): Follows the rules defined in the [style guide](./STYLE_GUIDE.md#versioning) for when to bump major, minor, and patch versions in individual contracts. +- [Individual Contract Versioning](#individual-contract-versioning): The versioning scheme for individual contracts and includes beta, release candidate, and feature tags. +- [Monorepo Contracts Release Versioning](#monorepo-contracts-release-versioning): The versioning scheme for monorepo smart contract releases. +- [Release Process](#release-process): The process for deploying contracts, creating a governance proposal, and the required associated releases. + - [Additional Release Candidates](#additional-release-candidates): How to handle additional release candidates after an initial `op-contracts/vX.Y.Z-rc.1` release. + - [Merging Back to Develop After Governance Approval](#merging-back-to-develop-after-governance-approval): Explains how to choose the resulting contract versions when merging back into `develop`. +- [Changelog](#changelog): A CHANGELOG for contract releases is maintained. + +> [!NOTE] +> The rules described in this document must be enforced manually. +> Ideally, a check can be added to CI to enforce the conventions defined here, but this is not currently implemented. + +## Semver Rules + +Version increments follow the [style guide rules](./STYLE_GUIDE.md#versioning) for when to bump major, minor, and patch versions in individual contracts: + +> - `patch` releases are to be used only for changes that do NOT modify contract bytecode (such as updating comments). +> - `minor` releases are to be used for changes that modify bytecode OR changes that expand the contract ABI provided that these changes do NOT break the existing interface. +> - `major` releases are to be used for changes that break the existing contract interface OR changes that modify the security model of a contract. +> +> Bumping the patch version does change the bytecode, so another exception is carved out for this. +> In other words, changing comments increments the patch version, which changes bytecode. This bytecode +> change implies a minor version increment is needed, but because it's just a version change, only a +> patch increment should be used. + +## Individual Contract Versioning + +Versioning for individual contracts works as follows: + +- A contract is only `X.Y.Z` on `develop` if it has been governance approved. If it's `X.Y.Z` before that, it must be on a branch. More on this below. +- For contracts undergoing development, a `-beta.n` identifier must be appended to the version number. +- For contracts in a release candidate state, an `-rc.n` identifier must be appended to the version number. +- For contracts with feature-specific changes, a `+feature-name` identifier must be appended to the version number. See the [Smart Contract Feature Development](https://github.com/ethereum-optimism/design-docs/blob/main/smart-contract-feature-development.md) design document to learn more. +- When making changes to a contract, always bump to the lowest possible version based on the specific change you are making. We do not want to e.g. optimistically bump to a major version, because protocol development sequencing may change unexpectedly. Use these examples to know how to bump the version: + - Example 1: A contract is currently on `1.2.3`. + - We don't yet know when the next release of this contract will be. However, you are simply fixing typos in comments so you bump the version to `1.2.4-beta.1`. + - The next PR made to that same contract clarifies some comments, so it bumps the version to `1.2.4-beta.2`. + - The next PR introduces a breaking change, which bumps the version from `1.2.4-beta.2` to `2.0.0-beta.1`. A `1.2.4-rc.1` and `1.2.4` version both never exist. + - Example 2: A contract is currently on `2.4.7`. + - We know the next release of this contract will be a breaking change. Regardless, as you start development by fixing typos in comments, bump the version to `2.4.8-beta.1`. This is because we may end up putting out a release before the breaking change is added. + - Once you start working on the breaking change, bump the version to `3.0.0-beta.1`. + - Continue to bump the beta version as you make changes. When the contract is ready for release, bump the version to `3.0.0-rc.1`. +- New contracts start at `1.0.0-beta.1`, increment the `-beta.n` counter during development, and become `1.0.0` when they are ready for production. + +## Monorepo Contracts Release Versioning + +Versioning for monorepo releases works as follows: + +- Monorepo releases continue to follow the `op-contracts/vX.Y.Z` naming convention. +- The version used for the next release is determined by the highest version bump of any individual contract in the release. + - Example 1: The monorepo is at `op-contracts/v1.5.0`. Clarifying comments are made in contracts, so all contracts only bump the patch version. The next monorepo release will be `op-contracts/v1.5.1`. + - Example 2: The monorepo is at `op-contracts/v1.5.1`. Various tech debt and code is cleaned up in contracts, but no features are added, so at most, contracts bumped the minor version. The next monorepo release will be `op-contracts/v1.6.0`. + - Example 3: The monorepo is at `op-contracts/v1.5.1`. Legacy `ALL_CAPS()` getter methods are removed from a contract, causing that contract to bump the major version. The next monorepo release will be `op-contracts/v2.0.0`. +- Feature specific monorepo releases (such as a beta release of the custom gas token feature) are supported, and should follow the guidelines in the [Smart Contract Feature Development](https://github.com/ethereum-optimism/design-docs/blob/main/smart-contract-feature-development.md) design doc. Bump the overall monorepo semver as required by the above rules, and append the `-beta,n` modifier to the version number. For example, if the last release before the custom gas token feature was `op-contracts/v1.5.1`, because the custom gas token introduces breaking changes, its beta release will be `op-contracts/v2.0.0-beta.n`. + - A subsequent release of the custom gas token feature that fixes bugs and introduces an additional breaking change would be `op-contracts/v2.0.0-beta.2`. + - This means `+feature-name` naming is not used for monorepo releases, only for individual contracts as described below. +- A monorepo contracts release must map to an exact set of contract semvers, and this mapping must be defined in the contract release notes which are the source of truth. See [`op-contracts/v1.4.0-rc.4`](https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.4.0-rc.4) for an example of what release notes should look like. + +## Release Process + +When a release is proposed to governance, the proposal includes a commit hash, and often the +contracts from that commit hash are already deployed to mainnet with their addresses included +in the proposal. +For example, the [Fault Proofs governance proposal](https://gov.optimism.io/t/upgrade-proposal-fault-proofs/8161) provides specific addresses that will be used. + +To accommodate this, once contract changes are ready for governance approval, the release flow is: + +- On the `develop` branch, bump the version of all contracts to their respective `X.Y.Z-rc.n`. The `X.Y.Z` here refers to the contract-specific versions, so it differs per-contract. The `-rc.n` begins as `-rc.1` for all contracts. Any `-beta.n` and `+feature-name` identifiers are removed at this point. +- Branch off of `develop` and create a branch named `proposal/op-contracts/vX.Y.Z`. Here, `X.Y.Z` is the new version of the monorepo release. + - Using the `proposal/` prefix signals that this branch is for a governance proposal, and intentionally does not convey whether or not the proposal has passed. +- Open a PR into the `proposal/op-contracts/vX.Y.Z` branch that removes the `-rc.1` suffixes from all contracts. + - This is the commit hash that will be tagged as `op-contracts/vX.Y.Z-rc.1`, used to deploy the contracts, and proposed to governance. + - Sometimes additional release candidates are needed before proposal—see [Additional Release Candidates](#additional-release-candidates) for more information on this flow. +- Once the governance approval is posted, any lock on contracts on `develop` is released. +- Once governance approves the proposal, merge the proposal branch into `develop` and set the version of all contracts to the appropriate `X.Y.Z` after considering any changes made to `develop` since the release candidate was created. + - See [Merging Back to Develop After Governance Approval](#merging-back-to-develop-after-governance-approval) for more information on how to choose the resulting contract versions when merging back into `develop`. + +### Additional Release Candidates + +Sometimes additional release candidate versions are needed. +The process for this is: + +- Make the fixes on `develop`. Increment the `-rc.n` qualifier for the changed contracts only. +- Open a PR into the `proposal/op-contracts/vX.Y.Z` branch that incorporates the changes from `develop`. +- Open another PR to remove the new `-rc.2` version identifiers from the changed contracts. Tag the resulting commit on the proposal branch as `op-contracts/vX.Y.Z-rc.2`. +- This flow (1) ensures develop stays up to date during the release process, (2) mitigates the risk of forgetting to merge the release back into the develop branch, and (3) mitigates the risk of the merge into develop removing the required `-rc.n` version that is needed until the release is approved. + +### Merging Back to Develop After Governance Approval + +A release will change a set of contracts, and those contracts may have changed on `develop` since the release candidate was created. + +If there have been no changes to a contract since the release candidate, the version of that contract stays at `X.Y.Z` and just has the `-rc.n` removed. +For example, if the release candidate is `1.2.3-rc.1`, the resulting version on `develop` will be `1.2.3`. + +If there have been changes to a contract, the `X.Y.Z` will stay the same as whatever is the latest version on `develop`, with the `-beta.n` qualifier incremented. + +For example, given that ContractA is `1.2.3-rc.1` on develop, then the initial sequence of events is: + +- We create the release branch, and on that branch remove the `-rc.1`, giving a final ContractA version on that branch of `1.2.3` +- Governance proposal is posted, pointing to the corresponding monorepo tag. +- Governance approves the release. +- Open a PR to merge the final versions of the contracts (ContractA) back into develop. + +Now there are two scenarios for the PR that merges the release branch back into develop: + +1. On develop, no changes have been made to ContractA. The PR therefore changes ContractA's version on develop from `1.2.3-rc.1` to `1.2.3`, and no other changes to ContractA occur. +2. On develop, breaking changes have been made to ContractA for a new feature, and it's currently versioned as `2.0.0-beta.3`. The PR should bump the version to `2.0.0-beta.4` if it changes the source code of ContractA. + - In practice, this one unlikely to occur when using inheritance for feature development, as specified in [Smart Contract Feature Development](https://github.com/ethereum-optimism/design-docs/blob/main/smart-contract-feature-development.md) architecture. It's more likely that (1) is the case, and we merge the version change into the base contract. + +This flow also provides a dedicated branch for each release, making it easy to deploy a patch or bug fix, regardless of other changes that may have occurred on develop since the release. + +## Changelog + +Lastly, a CHANGELOG for contract releases must be maintained: + +- Each upcoming release will have a tracking issue that documents the new versions of each contract that will be included in the release, along with links to the PRs that made the changes. +- Every contracts PR must have an accompanying changelog entry in a tracking issue once it is merged. +- Tracking issue titles should be named based on the expected Upgrade number they will go to governance with, e.g. "op-contracts changelog: Upgrade 9". + - See [ethereum-optimism/optimism#10592](https://github.com/ethereum-optimism/optimism/issues/10592) for an example of what this tracking issue should look like. + - We do not include a version number in the issue because it may be hard to predict the final version number of a release until all PRs are merged. + - Using upgrade numbers also acts as a forcing function to ensure upgrade sequencing and the governance process is accounted for early in the development process.
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/4202.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/4202.json rename from packages/contracts-bedrock/periphery-deploy-config/4202.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/4202.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/4460.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/4460.json rename from packages/contracts-bedrock/periphery-deploy-config/4460.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/4460.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/58008.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/58008.json rename from packages/contracts-bedrock/periphery-deploy-config/58008.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/58008.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/84532.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/84532.json rename from packages/contracts-bedrock/periphery-deploy-config/84532.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/84532.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/901.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/901.json rename from packages/contracts-bedrock/periphery-deploy-config/901.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/901.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/919.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/919.json rename from packages/contracts-bedrock/periphery-deploy-config/919.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/919.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/999999999.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/999999999.json rename from packages/contracts-bedrock/periphery-deploy-config/999999999.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/999999999.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/optimism-goerli.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/optimism-goerli.json rename from packages/contracts-bedrock/periphery-deploy-config/optimism-goerli.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/optimism-goerli.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/optimism-sepolia.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/optimism-sepolia.json rename from packages/contracts-bedrock/periphery-deploy-config/optimism-sepolia.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/optimism-sepolia.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/sepolia.json CELO/packages/contracts-bedrock/deploy-config-periphery/deploy/sepolia.json rename from packages/contracts-bedrock/periphery-deploy-config/sepolia.json rename to packages/contracts-bedrock/deploy-config-periphery/deploy/sepolia.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/drippie-config/sepolia-faucet-bridges.json CELO/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-faucet-bridges.json rename from packages/contracts-bedrock/periphery-deploy-config/drippie-config/sepolia-faucet-bridges.json rename to packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-faucet-bridges.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/drippie-config/sepolia-faucet-core.json CELO/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-faucet-core.json rename from packages/contracts-bedrock/periphery-deploy-config/drippie-config/sepolia-faucet-core.json rename to packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-faucet-core.json
diff --git OP/packages/contracts-bedrock/periphery-deploy-config/drippie-config/sepolia-ops.json CELO/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-ops.json rename from packages/contracts-bedrock/periphery-deploy-config/drippie-config/sepolia-ops.json rename to packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-ops.json
diff --git OP/packages/contracts-bedrock/deploy-config/devnetL1-template.json CELO/packages/contracts-bedrock/deploy-config/devnetL1-template.json index b2c991c27e657519fe95863748788adc45eaab4d..f8c2fbee4732c6ce09f991ab5aa13db83751e5a9 100644 --- OP/packages/contracts-bedrock/deploy-config/devnetL1-template.json +++ CELO/packages/contracts-bedrock/deploy-config/devnetL1-template.json @@ -70,5 +70,6 @@ "daCommitmentType": "KeccakCommitment", "daChallengeWindow": 160, "daResolveWindow": 160, "daBondSize": 1000000, - "daResolverRefundPercentage": 0 + "daResolverRefundPercentage": 0, + "deployCeloContracts": true }
diff --git OP/packages/contracts-bedrock/foundry.toml CELO/packages/contracts-bedrock/foundry.toml index b408087239f542f4d4b874b14dfe67a348c031c0..bc73903aca533e2889ce6fdd71e9dc5067c9d162 100644 --- OP/packages/contracts-bedrock/foundry.toml +++ CELO/packages/contracts-bedrock/foundry.toml @@ -12,6 +12,7 @@ optimizer = true optimizer_runs = 999999 remappings = [ '@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts', + '@multicall/=lib/multicall/src', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts', '@rari-capital/solmate/=lib/solmate', '@lib-keccak/=lib/lib-keccak/contracts/lib', @@ -36,12 +37,13 @@ { access='read-write', path='./.resource-metering.csv' }, { access='read-write', path='./snapshots/' }, { access='read-write', path='./deployments/' }, { access='read', path='./deploy-config/' }, - { access='read', path='./periphery-deploy-config/' }, + { access='read', path='./deploy-config-periphery/' }, { access='read', path='./broadcast/' }, { access='read', path = './forge-artifacts/' }, { access='write', path='./semver-lock.json' }, { access='read-write', path='./.testdata/' }, - { access='read', path='./kout-deployment' } + { access='read', path='./kout-deployment' }, + { access='read-write', path='../../op-chain-ops/cmd/celo-migrate/testdata/' }, ] libs = ["node_modules", "lib"]
diff --git OP/packages/contracts-bedrock/lib/multicall/LICENSE CELO/packages/contracts-bedrock/lib/multicall/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2cc7ab2d68b7e8160d2d12153b975ef327c94ae9 --- /dev/null +++ CELO/packages/contracts-bedrock/lib/multicall/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Matt Solomon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
diff --git OP/packages/contracts-bedrock/lib/multicall/README.md CELO/packages/contracts-bedrock/lib/multicall/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a5b889b1157161fad3cd23a03316bca6fb80cada --- /dev/null +++ CELO/packages/contracts-bedrock/lib/multicall/README.md @@ -0,0 +1,5 @@ +Multicall3 contract for local devnet. +This is required by viem for using the op-stack functionality. + +Contract taken from: +https://github.com/mds1/multicall/commit/d7b62458c99c650ce1efa7464ffad69d2059ad56
diff --git OP/packages/contracts-bedrock/package.json CELO/packages/contracts-bedrock/package.json index 13d1b79d933485a61553426b2bb0d6b1b171f596..adf176d3dfde8a4c77bffe607060602311a796f7 100644 --- OP/packages/contracts-bedrock/package.json +++ CELO/packages/contracts-bedrock/package.json @@ -12,6 +12,7 @@ ], "scripts": { "prebuild": "./scripts/checks/check-foundry-install.sh", "build": "forge build", + "build:linkedLibraries": "forge build --libraries src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol:AddressSortedLinkedListWithMedian:0xED477A99035d0c1e11369F1D7A4e587893cc002B", "build:go-ffi": "(cd scripts/go-ffi && go build)", "autogen:invariant-docs": "npx tsx scripts/autogen/generate-invariant-docs.ts", "test": "pnpm build:go-ffi && forge test", @@ -19,7 +20,7 @@ "test:kontrol": "./test/kontrol/scripts/run-kontrol.sh script", "genesis": "forge script scripts/L2Genesis.s.sol:L2Genesis --sig 'runWithStateDump()'", "coverage": "pnpm build:go-ffi && (forge coverage || (bash -c \"forge coverage 2>&1 | grep -q 'Stack too deep' && echo -e '\\033[1;33mWARNING\\033[0m: Coverage failed with stack too deep, so overriding and exiting successfully' && exit 0 || exit 1\"))", "coverage:lcov": "pnpm build:go-ffi && (forge coverage --report lcov || (bash -c \"forge coverage --report lcov 2>&1 | grep -q 'Stack too deep' && echo -e '\\033[1;33mWARNING\\033[0m: Coverage failed with stack too deep, so overriding and exiting successfully' && exit 0 || exit 1\"))", - "deploy": "./scripts/deploy.sh", + "deploy": "./scripts/deploy/deploy.sh", "gas-snapshot:no-build": "forge snapshot --match-contract GasBenchMark", "statediff": "./scripts/statediff.sh && git diff --exit-code", "gas-snapshot": "pnpm build:go-ffi && pnpm gas-snapshot:no-build",
diff --git OP/packages/contracts-bedrock/scripts/ChainAssertions.sol CELO/packages/contracts-bedrock/scripts/ChainAssertions.sol index a99c14e9514bce2c12a5e3e0782fa1e87f2dd945..7022910b38c1fe56698c80165c028b7ca96c9424 100644 --- OP/packages/contracts-bedrock/scripts/ChainAssertions.sol +++ CELO/packages/contracts-bedrock/scripts/ChainAssertions.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0;   import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol"; -import { DeployConfig } from "scripts/DeployConfig.s.sol"; -import { Deployer } from "scripts/Deployer.sol"; +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; +import { Deployer } from "scripts/deploy/Deployer.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; import { Constants } from "src/libraries/Constants.sol"; import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; @@ -23,6 +23,8 @@ import { Types } from "scripts/Types.sol"; import { Vm } from "forge-std/Vm.sol"; import { ISystemConfigV0 } from "scripts/interfaces/ISystemConfigV0.sol"; import { console2 as console } from "forge-std/console2.sol"; + +import { CeloTokenL1 } from "src/celo/CeloTokenL1.sol";   library ChainAssertions { Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); @@ -412,5 +414,20 @@ require( uint8((uint256(slotVal) >> (_offset * 8)) & 0xFF) == uint8(1), "Storage value is not 1 at the given slot and offset" ); + } + + /// @notice Asserts the CeloTokenL1 is setup correctly + function checkCeloTokenL1(Types.ContractSet memory _contracts, bool _isProxy) internal view { + console.log("Running chain assertions on the CeloTokenL1"); + + CeloTokenL1 celoToken = CeloTokenL1(payable(_contracts.CustomGasToken)); + + // Check that the contract is initialized + assertSlotValueIsOne({ _contractAddress: address(celoToken), _slot: 0, _offset: 0 }); + + if (_isProxy) { + require(celoToken.totalSupply() == 1000000000e18); // 1 billion CELO + require(celoToken.balanceOf(_contracts.OptimismPortal) == 1000000000e18); + } } }
diff --git OP/packages/contracts-bedrock/scripts/L2Genesis.s.sol CELO/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 44607c53465089d6095be12ac4208b344f8dc001..7a67307cd71b9d0ce4e9ccfeaf3fc647a53be574 100644 --- OP/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ CELO/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -2,12 +2,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15;   import { Script } from "forge-std/Script.sol"; +import { stdJson } from "forge-std/StdJson.sol"; import { console2 as console } from "forge-std/console2.sol"; -import { Deployer } from "scripts/Deployer.sol"; +import { Deployer } from "scripts/deploy/Deployer.sol";   import { Config, OutputMode, OutputModeUtils, Fork, ForkUtils, LATEST_FORK } from "scripts/Config.sol"; import { Artifacts } from "scripts/Artifacts.s.sol"; -import { DeployConfig } from "scripts/DeployConfig.s.sol"; +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol"; import { L2CrossDomainMessenger } from "src/L2/L2CrossDomainMessenger.sol"; @@ -26,6 +27,18 @@ import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; import { FeeVault } from "src/universal/FeeVault.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Process } from "scripts/libraries/Process.sol"; +import { GoldToken } from "src/celo/GoldToken.sol"; + +import { CeloPredeploys } from "src/celo/CeloPredeploys.sol"; +import { CeloRegistry } from "src/celo/CeloRegistry.sol"; +import { FeeHandler } from "src/celo/FeeHandler.sol"; +import { MentoFeeHandlerSeller } from "src/celo/MentoFeeHandlerSeller.sol"; +import { UniswapFeeHandlerSeller } from "src/celo/UniswapFeeHandlerSeller.sol"; +import { SortedOracles } from "src/celo/stability/SortedOracles.sol"; +import { FeeCurrencyDirectory } from "src/celo/FeeCurrencyDirectory.sol"; +import { FeeCurrency } from "src/celo/testing/FeeCurrency.sol"; +import { AddressSortedLinkedListWithMedian } from "src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol"; +import { StableTokenV2 } from "src/celo/StableTokenV2.sol";   interface IInitializable { function initialize(address _addr) external; @@ -91,9 +104,43 @@ /// @notice The address of the deployer account. address internal deployer;   + // celo - create and write predeploy map + mapping(string => address) public deployedContractNamesToAddresses; + string internal _celoL2Outfile; + + function celoL2Outfile() internal view returns (string memory _env) { + _env = vm.envOr( + "L2_OUTFILE", + string.concat(vm.projectRoot(), "/deployments/", vm.toString(block.chainid), "-l2-deploy.json") + ); + } + + function celoSave(string memory _name, address _impl, address _proxy) public { + if (deployedContractNamesToAddresses[_name] == address(0)) { + deployedContractNamesToAddresses[_name] = _impl; + + _celoWrite(_name, _impl); + } + + if (_proxy != address(0)) { + string memory _proxyName = string.concat(_name, "Proxy"); + deployedContractNamesToAddresses[_proxyName] = _proxy; + + _celoWrite(_proxyName, _proxy); + } + } + + function _celoWrite(string memory _name, address _deployed) internal { + console.log("Writing l2 deploy %s: %s", _name, _deployed); + + vm.writeJson({ json: stdJson.serialize("celo_l2_deploys", _name, _deployed), path: _celoL2Outfile }); + } + /// @notice Sets up the script and ensures the deployer account is used to make calls. function setUp() public override { deployer = makeAddr("deployer"); + _celoL2Outfile = celoL2Outfile(); + super.setUp(); }   @@ -134,10 +181,15 @@ console.log("L2Genesis: outputMode: %s, fork: %s", _mode.toString(), _fork.toString()); vm.startPrank(deployer); vm.chainId(cfg.l2ChainID());   - dealEthToPrecompiles(); + if (cfg.deployCeloContracts()) { + dealEthToPrecompiles(); + } setPredeployProxies(); setPredeployImplementations(_l1Dependencies); setPreinstalls(); + if (cfg.deployCeloContracts()) { + setCeloPredeploys(); + } if (cfg.fundDevAccounts()) { fundDevAccounts(); } @@ -206,7 +258,10 @@ if (Predeploys.isSupportedPredeploy(addr, cfg.useInterop())) { address implementation = Predeploys.predeployToCodeNamespace(addr); console.log("Setting proxy %s implementation: %s", addr, implementation); + string memory name = Predeploys.getName(addr); EIP1967Helper.setImplementation(addr, implementation); + + celoSave(name, implementation, addr); } } } @@ -366,6 +421,7 @@ /// This contract is NOT proxied and the state that is set /// in the constructor is set manually. function setWETH() public { console.log("Setting %s implementation at: %s", "WETH", Predeploys.WETH); + celoSave("WETH", Predeploys.WETH, address(0)); vm.etch(Predeploys.WETH, vm.getDeployedCode("WETH.sol:WETH")); }   @@ -423,6 +479,7 @@ GovernanceToken token = new GovernanceToken(); console.log("Setting %s implementation at: %s", "GovernanceToken", Predeploys.GOVERNANCE_TOKEN); vm.etch(Predeploys.GOVERNANCE_TOKEN, address(token).code); + celoSave("GovernanceToken", Predeploys.GOVERNANCE_TOKEN, address(0));   bytes32 _nameSlot = hex"0000000000000000000000000000000000000000000000000000000000000003"; bytes32 _symbolSlot = hex"0000000000000000000000000000000000000000000000000000000000000004"; @@ -529,6 +586,7 @@ /// @notice Sets the bytecode in state function _setPreinstallCode(address _addr) internal { string memory cname = Preinstalls.getName(_addr); console.log("Setting %s preinstall code at: %s", cname, _addr); + celoSave(cname, _addr, address(0)); vm.etch(_addr, Preinstalls.getDeployedCode(_addr, cfg.l2ChainID())); // during testing in a shared L1/L2 account namespace some preinstalls may already have been inserted and used. if (vm.getNonce(_addr) == 0) { @@ -567,5 +625,183 @@ for (uint256 i; i < devAccounts.length; i++) { console.log("Funding dev account %s with %s ETH", devAccounts[i], DEV_ACCOUNT_FUND_AMT / 1e18); vm.deal(devAccounts[i], DEV_ACCOUNT_FUND_AMT); } + } + + ///@notice Sets all proxies and implementations for Celo contracts + function setCeloPredeploys() internal { + console.log("Deploying Celo contracts"); + + setCeloRegistry(); + setCeloGoldToken(); + setCeloFeeHandler(); + setCeloMentoFeeHandlerSeller(); + setCeloUniswapFeeHandlerSeller(); + // setCeloSortedOracles(); + // setCeloAddressSortedLinkedListWithMedian(); + setCeloFeeCurrency(); + setFeeCurrencyDirectory(); + + address[] memory initialBalanceAddresses = new address[](1); + initialBalanceAddresses[0] = devAccounts[0]; + + uint256[] memory initialBalances = new uint256[](1); + initialBalances[0] = 100_000 ether; + //deploycUSD(initialBalanceAddresses, initialBalances, 2); + } + + /// @notice Sets up a proxy for the given impl address + function _setupProxy(address addr, address impl) internal returns (address) { + bytes memory code = vm.getDeployedCode("Proxy.sol:Proxy"); + vm.etch(addr, code); + EIP1967Helper.setAdmin(addr, Predeploys.PROXY_ADMIN); + + console.log("Setting proxy %s with implementation: %s", addr, impl); + EIP1967Helper.setImplementation(addr, impl); + + return addr; + } + + function setCeloRegistry() internal { + CeloRegistry kontract = new CeloRegistry({ test: false }); + + address precompile = CeloPredeploys.CELO_REGISTRY; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloGoldToken() internal { + GoldToken kontract = new GoldToken({ test: false }); + + address precompile = CeloPredeploys.GOLD_TOKEN; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloFeeHandler() internal { + FeeHandler kontract = new FeeHandler({ test: false }); + + address precompile = CeloPredeploys.FEE_HANDLER; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloMentoFeeHandlerSeller() internal { + MentoFeeHandlerSeller kontract = new MentoFeeHandlerSeller({ test: false }); + + address precompile = CeloPredeploys.MENTO_FEE_HANDLER_SELLER; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloUniswapFeeHandlerSeller() internal { + UniswapFeeHandlerSeller kontract = new UniswapFeeHandlerSeller({ test: false }); + + address precompile = CeloPredeploys.UNISWAP_FEE_HANDLER_SELLER; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloSortedOracles() internal { + SortedOracles kontract = new SortedOracles({ test: false }); + + address precompile = CeloPredeploys.SORTED_ORACLES; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setFeeCurrencyDirectory() internal { + FeeCurrencyDirectory feeCurrencyDirectory = new FeeCurrencyDirectory({ test: false }); + + address precompile = CeloPredeploys.FEE_CURRENCY_DIRECTORY; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(feeCurrencyDirectory)); + + vm.resetNonce(address(feeCurrencyDirectory)); + _setupProxy(precompile, address(feeCurrencyDirectory)); + + vm.startPrank(devAccounts[0]); + FeeCurrencyDirectory(precompile).initialize(); + vm.stopPrank(); + } + + // function setCeloAddressSortedLinkedListWithMedian() internal { + // AddressSortedLinkedListWithMedian kontract = new AddressSortedLinkedListWithMedian({ + // }); + // address precompile = CeloPredeploys.ADDRESS_SORTED_LINKED_LIST_WITH_MEDIAN; + // string memory cname = CeloPredeploys.getName(precompile); + // console.log("Deploying %s implementation at: %s", cname, address(kontract )); + // vm.resetNonce(address(kontract )); + // _setupProxy(precompile, address(kontract)); + // } + + function setCeloFeeCurrency() internal { + FeeCurrency kontract = new FeeCurrency({ name_: "Test", symbol_: "TST" }); + address precompile = CeloPredeploys.FEE_CURRENCY; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function deploycUSD( + address[] memory initialBalanceAddresses, + uint256[] memory initialBalanceValues, + uint256 celoPrice + ) + public + { + StableTokenV2 kontract = new StableTokenV2({ disable: false }); + address cusdProxyAddress = CeloPredeploys.cUSD; + string memory cname = CeloPredeploys.getName(cusdProxyAddress); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + vm.resetNonce(address(kontract)); + + _setupProxy(cusdProxyAddress, address(kontract)); + + kontract.initialize("Celo Dollar", "cUSD", initialBalanceAddresses, initialBalanceValues); + + SortedOracles sortedOracles = SortedOracles(CeloPredeploys.SORTED_ORACLES); + + console.log("beofre add oracle"); + + vm.startPrank(sortedOracles.owner()); + sortedOracles.addOracle(cusdProxyAddress, deployer); + vm.stopPrank(); + vm.startPrank(deployer); + + if (celoPrice != 0) { + sortedOracles.report(cusdProxyAddress, celoPrice * 1e24, address(0), address(0)); // TODO use fixidity + } + + /* + Arbitrary intrinsic gas number take from existing `FeeCurrencyDirectory.t.sol` tests + Source: + https://github.com/celo-org/celo-monorepo/blob/2cec07d43328cf4216c62491a35eacc4960fffb6/packages/protocol/test-sol/common/FeeCurrencyDirectory.t.sol#L27 + */ + uint256 mockIntrinsicGas = 21000; + + FeeCurrencyDirectory feeCurrencyDirectory = FeeCurrencyDirectory(CeloPredeploys.FEE_CURRENCY_DIRECTORY); + vm.startPrank(feeCurrencyDirectory.owner()); + feeCurrencyDirectory.setCurrencyConfig(cusdProxyAddress, address(sortedOracles), mockIntrinsicGas); + vm.stopPrank(); + vm.startPrank(deployer); } }
diff --git OP/packages/contracts-bedrock/scripts/Types.sol CELO/packages/contracts-bedrock/scripts/Types.sol index c8b48dc1e704873432e2ded13a4c46c54c2247ab..b7e4c6a1cdce15d263ac6aad95073cdc157f4a23 100644 --- OP/packages/contracts-bedrock/scripts/Types.sol +++ CELO/packages/contracts-bedrock/scripts/Types.sol @@ -17,5 +17,6 @@ address SystemConfig; address L1ERC721Bridge; address ProtocolVersions; address SuperchainConfig; + address CustomGasToken; } }
diff --git OP/packages/contracts-bedrock/scripts/contract_map.sh CELO/packages/contracts-bedrock/scripts/contract_map.sh new file mode 100755 index 0000000000000000000000000000000000000000..caa8a716a13baf252dfecd1ffdf447606a1b8983 --- /dev/null +++ CELO/packages/contracts-bedrock/scripts/contract_map.sh @@ -0,0 +1,140 @@ +#!/bin/bash +set -o pipefail + +L1_URL="${1:?Must specify L1 RPC URL}" +L1_ADDRESSES="${2:?Must specify L1 addresses json}" +OUTPUT="${3:-relations}" + +addresses=$(jq -r '.[]' "$L1_ADDRESSES") + +contract_addresses=() +processed_addresses=() +dots=() + +while IFS= read -r address; do + contract_addresses+=("$address") +done <<< "$addresses" + +address_exists() { + local addr="$1" + for processed_addr in "${processed_addresses[@]}"; do + if [[ "$processed_addr" == "$addr" ]]; then + return 0 + fi + done + return 1 +} + +check_admin() { + local addr="$1" + admin=$(cast adm "$addr" --rpc-url "$L1_URL") + + if [[ $? == 0 && "$admin" != "0x0000000000000000000000000000000000000000" ]]; then + contract_addresses+=( "$admin" ) + echo " -> Admin: $admin" + add_relation "$admin" "$addr" "admin" + fi + + return 0 +} + +check_owners() { + local addr="$1" + + # suppressing stderr (and unset -e) as failure is expected when this abi does not exist + # getOwners defined in OwnerManager on GnosisSafe contract + if owners=$(cast call "$addr" --rpc-url "$L1_URL" 'getOwners()(address[])' 2>/dev/null) ; then + # trim pseudo json output + tr=$(echo "$owners" | tr -d '[],') + owners_arr=( "$tr" ) + + # Iterate over the values + for owner in "${owners_arr[@]}"; do + echo " -> Multisig Owner: $owner" + add_relation "$owner" "$addr" "multisig_owner" + contract_addresses+=( "$owner" ) + done + fi + + # owner defined in Ownable on OpenZeppelin abstract contract + if owner=$(cast call "$addr" --rpc-url "$L1_URL" 'owner()(address)' 2>/dev/null) ; then + echo " -> Owner: $owner" + add_relation "$owner" "$addr" "owner" + contract_addresses+=( "$owner" ) + fi + + return 0 +} + +check_implementation() { + local addr="$1" + + impl=$(cast implementation "$addr" --rpc-url "$L1_URL") + + if [[ $? == 0 && "$impl" != "0x0000000000000000000000000000000000000000" ]]; then + contract_addresses+=( "$impl" ) + echo " -> Impl: $impl" + add_relation "$addr" "$impl" "proxies" + fi + + return 0 +} + +get_name() { + local addr + local result + + addr=$(cast to-check-sum-address "$1") + result=$(jq -r "to_entries | map(select(.value == \"$addr\")) | .[0].key" "$L1_ADDRESSES") + + if [[ ${#result} -gt 4 ]]; then + printf "%s\n(%s)" "$addr" "$result" + else + echo "$addr" + fi +} + +add_relation() { + local source="$1" + local destination="$2" + local label="$3" + + local source_name + local destination_name + + source_name=$(get_name "$source") + destination_name=$(get_name "$destination") + + dots+=("\"$source_name\" -> \"$destination_name\"[label = \"$label\"];") +} + +# while loop to allow for modification of the array during iteration +i=0 +while [ $i -lt ${#contract_addresses[@]} ]; do + address="$(cast to-check-sum-address "${contract_addresses[$i]}")" + if address_exists "$address"; then + # already processed this address, skip iteration + i=$((i + 1)) + continue + fi + + echo "Checking $address" + + check_admin "$address" + check_owners "$address" + check_implementation "$address" + + processed_addresses+=("$address") + i=$((i + 1)) +done + +# write out chart +echo "digraph {" > "$OUTPUT".dot +echo "rankdir=\"LR\";" >> "$OUTPUT".dot +for dot in "${dots[@]}"; do + echo "$dot" >> "$OUTPUT".dot +done +echo "}" >> "$OUTPUT".dot + +dot "$OUTPUT".dot -Tpng -o "$OUTPUT".png +open "$OUTPUT".png
diff --git OP/packages/contracts-bedrock/scripts/deploy.sh CELO/packages/contracts-bedrock/scripts/deploy.sh deleted file mode 100755 index bfbd436eb5fafd0bf54b7dce76da523d28aab6be..0000000000000000000000000000000000000000 --- OP/packages/contracts-bedrock/scripts/deploy.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -verify_flag="" -if [ -n "${DEPLOY_VERIFY:-}" ]; then - verify_flag="--verify" -fi - -echo "> Deploying contracts" -forge script -vvv scripts/Deploy.s.sol:Deploy --rpc-url "$DEPLOY_ETH_RPC_URL" --broadcast --private-key "$DEPLOY_PRIVATE_KEY" $verify_flag - -if [ -n "${DEPLOY_GENERATE_HARDHAT_ARTIFACTS:-}" ]; then - echo "> Generating hardhat artifacts" - forge script -vvv scripts/Deploy.s.sol:Deploy --sig 'sync()' --rpc-url "$DEPLOY_ETH_RPC_URL" --broadcast --private-key "$DEPLOY_PRIVATE_KEY" -fi
diff --git OP/packages/contracts-bedrock/scripts/Deploy.s.sol CELO/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol rename from packages/contracts-bedrock/scripts/Deploy.s.sol rename to packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 4b24d45926bc87e0c839e8e8cf18214153965086..58860fc4d72289417918f94121cd79999c44363a 100644 --- OP/packages/contracts-bedrock/scripts/Deploy.s.sol +++ CELO/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -12,7 +12,7 @@ import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol"; import { Enum as SafeOps } from "safe-contracts/common/Enum.sol";   -import { Deployer } from "scripts/Deployer.sol"; +import { Deployer } from "scripts/deploy/Deployer.sol";   import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { AddressManager } from "src/legacy/AddressManager.sol"; @@ -57,6 +57,10 @@ import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol"; import { Process } from "scripts/libraries/Process.sol"; + +import { CeloTokenL1 } from "src/celo/CeloTokenL1.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Multicall3 } from "@multicall/Multicall3.sol";   /// @title Deploy /// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function. @@ -158,7 +162,10 @@ OptimismPortal2: mustGetAddress("OptimismPortalProxy"), SystemConfig: mustGetAddress("SystemConfigProxy"), L1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"), ProtocolVersions: mustGetAddress("ProtocolVersionsProxy"), - SuperchainConfig: mustGetAddress("SuperchainConfigProxy") + SuperchainConfig: mustGetAddress("SuperchainConfigProxy"), + // allow for address(0) since it is not strictly required for all + // combinations of chain configs + CustomGasToken: getAddress("CustomGasTokenProxy") }); }   @@ -177,7 +184,8 @@ OptimismPortal2: getAddress("OptimismPortalProxy"), SystemConfig: getAddress("SystemConfigProxy"), L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"), ProtocolVersions: getAddress("ProtocolVersionsProxy"), - SuperchainConfig: getAddress("SuperchainConfigProxy") + SuperchainConfig: getAddress("SuperchainConfigProxy"), + CustomGasToken: getAddress("CustomGasTokenProxy") }); }   @@ -392,11 +400,17 @@ deployDelayedWETH(); deployPreimageOracle(); deployMips(); deployAnchorStateRegistry(); + + // Multicall3 + deployMulticall3(); }   /// @notice Initialize all of the implementations function initializeImplementations() public { console.log("Initializing implementations"); + + setupCustomGasToken(); + // Selectively initialize either the original OptimismPortal or the new OptimismPortal2. Since this will upgrade // the proxy, we cannot initialize both. if (cfg.useFaultProofs()) { @@ -1234,6 +1248,14 @@ address l2OutputOracleProxy = mustGetAddress("L2OutputOracleProxy"); address systemConfigProxy = mustGetAddress("SystemConfigProxy"); address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy");   + address customGasTokenAddress = Constants.ETHER; + uint256 initialBalance = 0; + if (cfg.useCustomGasToken()) { + customGasTokenAddress = cfg.customGasTokenAddress(); + IERC20 token = IERC20(customGasTokenAddress); + initialBalance = token.balanceOf(optimismPortalProxy); + } + _upgradeAndCallViaSafe({ _proxy: payable(optimismPortalProxy), _implementation: optimismPortal, @@ -1242,7 +1264,8 @@ OptimismPortal.initialize, ( L2OutputOracle(l2OutputOracleProxy), SystemConfig(systemConfigProxy), - SuperchainConfig(superchainConfigProxy) + SuperchainConfig(superchainConfigProxy), + initialBalance ) ) }); @@ -1559,5 +1582,54 @@ require(dac.challengeWindow() == daChallengeWindow); require(dac.resolveWindow() == daResolveWindow); require(dac.bondSize() == daBondSize); require(dac.resolverRefundPercentage() == daResolverRefundPercentage); + } + + function setupCustomGasToken() internal { + if (cfg.useCustomGasToken() && cfg.customGasTokenAddress() == address(0)) { + deployERC1967Proxy("CustomGasTokenProxy"); + + console.log("Setting up Custom gas token"); + deployCustomGasToken(); + initializeCustomGasToken(); + + address proxyAddress = mustGetAddress("CustomGasTokenProxy"); + cfg.setUseCustomGasToken(proxyAddress); + } + } + + function deployCustomGasToken() public broadcast returns (address addr_) { + console.log("Deploying CustomGasToken implementation"); + + CeloTokenL1 customGasToken = new CeloTokenL1{ salt: _implSalt() }(); + + save("CustomGasToken", address(customGasToken)); + console.log("CustomGasToken deployed at %s", address(customGasToken)); + addr_ = address(customGasToken); + } + + /// @notice Initialize the CustomGasToken + function initializeCustomGasToken() public broadcast { + console.log("Upgrading and initializing CustomGasToken proxy"); + address customGasTokenProxyAddress = mustGetAddress("CustomGasTokenProxy"); + address customGasTokenAddress = mustGetAddress("CustomGasToken"); + address portalProxyAddress = mustGetAddress("OptimismPortalProxy"); + + _upgradeAndCallViaSafe({ + _proxy: payable(customGasTokenProxyAddress), + _implementation: customGasTokenAddress, + _innerCallData: abi.encodeCall(CeloTokenL1.initialize, (portalProxyAddress)) + }); + + ChainAssertions.checkCeloTokenL1({ _contracts: _proxies(), _isProxy: false }); + } + + function deployMulticall3() internal onlyDevnet returns (address addr_) { + // Necessary to be deployed on the L1 for viems withdraw logic + // Only necessary on local devnet, since on the common public testnets + // the multicall3 is already deployed. + console.log("Deploying up Multicall3 contact"); + Multicall3 mc3 = new Multicall3(); + addr_ = address(mc3); + save("Multicall3", addr_); } }
diff --git OP/packages/contracts-bedrock/scripts/DeployConfig.s.sol CELO/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol rename from packages/contracts-bedrock/scripts/DeployConfig.s.sol rename to packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol index 25869e97f0807563f327c6047d65cdb0b9156869..5faf1b94b3bddf08e49728392f44d7a5378731d2 100644 --- OP/packages/contracts-bedrock/scripts/DeployConfig.s.sol +++ CELO/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol @@ -91,6 +91,8 @@ address public customGasTokenAddress;   bool public useInterop;   + bool public deployCeloContracts; + function read(string memory _path) public { console.log("DeployConfig: reading file %s", _path); try vm.readFile(_path) returns (string memory data) { @@ -174,6 +176,9 @@ useCustomGasToken = _readOr(_json, "$.useCustomGasToken", false); customGasTokenAddress = _readOr(_json, "$.customGasTokenAddress", address(0));   useInterop = _readOr(_json, "$.useInterop", false); + + // Celo specific config + deployCeloContracts = _readOr(_json, "$.deployCeloContracts", false); }   function fork() public view returns (Fork fork_) { @@ -234,6 +239,11 @@ /// @notice Allow the `fundDevAccounts` config to be overridden. function setFundDevAccounts(bool _fundDevAccounts) public { fundDevAccounts = _fundDevAccounts; + } + + /// @notice Allow the `deployCeloContracts` config to be overridden. + function setDeployCeloContracts(bool _deployCeloContracts) public { + deployCeloContracts = _deployCeloContracts; }   /// @notice Allow the `useCustomGasToken` config to be overridden in testing environments
diff --git OP/packages/contracts-bedrock/scripts/DeployOwnership.s.sol CELO/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol rename from packages/contracts-bedrock/scripts/DeployOwnership.s.sol rename to packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol index bb436d69b15b37374db542726fd2e9e6d099f801..05fbfd54df93711c6bed69461abda1ca4d5513a5 100644 --- OP/packages/contracts-bedrock/scripts/DeployOwnership.s.sol +++ CELO/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol @@ -9,7 +9,7 @@ import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; import { ModuleManager } from "safe-contracts/base/ModuleManager.sol"; import { GuardManager } from "safe-contracts/base/GuardManager.sol";   -import { Deployer } from "scripts/Deployer.sol"; +import { Deployer } from "scripts/deploy/Deployer.sol";   import { LivenessGuard } from "src/Safe/LivenessGuard.sol"; import { LivenessModule } from "src/Safe/LivenessModule.sol";
diff --git OP/packages/contracts-bedrock/scripts/Deployer.sol CELO/packages/contracts-bedrock/scripts/deploy/Deployer.sol rename from packages/contracts-bedrock/scripts/Deployer.sol rename to packages/contracts-bedrock/scripts/deploy/Deployer.sol index aac3f5ac8ec2535c310fa3763971bbd6efbc0288..2a861ba34608416e7d1ea78d3958a37ca818c6a1 100644 --- OP/packages/contracts-bedrock/scripts/Deployer.sol +++ CELO/packages/contracts-bedrock/scripts/deploy/Deployer.sol @@ -4,7 +4,7 @@ import { Script } from "forge-std/Script.sol"; import { Artifacts } from "scripts/Artifacts.s.sol"; import { Config } from "scripts/Config.sol"; -import { DeployConfig } from "scripts/DeployConfig.s.sol"; +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; import { Executables } from "scripts/Executables.sol"; import { console } from "forge-std/console.sol";
diff --git OP/packages/contracts-bedrock/scripts/deploy/deploy.sh CELO/packages/contracts-bedrock/scripts/deploy/deploy.sh new file mode 100755 index 0000000000000000000000000000000000000000..bc497e0b8568a11a5b6166b64915ff42049df8f1 --- /dev/null +++ CELO/packages/contracts-bedrock/scripts/deploy/deploy.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +verify_flag="" +if [ -n "${DEPLOY_VERIFY:-}" ]; then + verify_flag="--verify" +fi + +echo "> Deploying contracts" +forge script -vvv scripts/deploy/Deploy.s.sol:Deploy --rpc-url "$DEPLOY_ETH_RPC_URL" --broadcast --private-key "$DEPLOY_PRIVATE_KEY" $verify_flag + +if [ -n "${DEPLOY_GENERATE_HARDHAT_ARTIFACTS:-}" ]; then + echo "> Generating hardhat artifacts" + forge script -vvv scripts/deploy/Deploy.s.sol:Deploy --sig 'sync()' --rpc-url "$DEPLOY_ETH_RPC_URL" --broadcast --private-key "$DEPLOY_PRIVATE_KEY" +fi
diff --git OP/packages/contracts-bedrock/scripts/fpac/FPACOPS.s.sol CELO/packages/contracts-bedrock/scripts/fpac/FPACOPS.s.sol index 434b5274d9290e9494b8f7fcadd869cdb11193a9..72011adfc896a784659d7c8638fba6325b7467c4 100644 --- OP/packages/contracts-bedrock/scripts/fpac/FPACOPS.s.sol +++ CELO/packages/contracts-bedrock/scripts/fpac/FPACOPS.s.sol @@ -7,7 +7,7 @@ import { AnchorStateRegistry, IAnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; import { StdAssertions } from "forge-std/StdAssertions.sol"; import "src/dispute/lib/Types.sol"; -import "scripts/Deploy.s.sol"; +import "scripts/deploy/Deploy.s.sol";   /// @notice Deploys the Fault Proof Alpha Chad contracts. contract FPACOPS is Deploy, StdAssertions {
diff --git OP/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh CELO/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh new file mode 100755 index 0000000000000000000000000000000000000000..337669d4fbaed5ff2a8c23e0f432fd079663c2a4 --- /dev/null +++ CELO/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash + +# This script is used to generate the getting-started.json configuration file +# used in the Getting Started quickstart guide on the docs site. Avoids the +# need to have the getting-started.json committed to the repo since it's an +# invalid JSON file when not filled in, which is annoying. + +reqenv() { + if [ -z "${!1}" ]; then + echo "Error: environment variable '$1' is undefined" + exit 1 + fi +} + +# Check required environment variables +reqenv "DEPLOYMENT_CONTEXT" +reqenv "GS_ADMIN_ADDRESS" +reqenv "GS_BATCHER_ADDRESS" +reqenv "GS_PROPOSER_ADDRESS" +reqenv "GS_SEQUENCER_ADDRESS" +reqenv "L1_RPC_URL" +reqenv "L1_CHAIN_ID" +reqenv "L2_CHAIN_ID" +reqenv "L1_BLOCK_TIME" +reqenv "L2_BLOCK_TIME" +reqenv "FUNDS_DEV_ACCOUNTS" +reqenv "USE_PLASMA" +reqenv "DEPLOY_CELO_CONTRACTS" +reqenv "USE_CUSTOM_GAS_TOKEN" +reqenv "CUSTOM_GAS_TOKEN_ADDRESS" + +# Get the finalized block timestamp and hash +block=$(cast block finalized --rpc-url "$L1_RPC_URL") +timestamp=$(echo "$block" | awk '/timestamp/ { print $2 }') +blockhash=$(echo "$block" | awk '/hash/ { print $2 }') +batchInboxAddressSuffix=$(printf "%0$((38 - ${#L2_CHAIN_ID}))d" 0)$L2_CHAIN_ID +batchInboxAddress=0xff$batchInboxAddressSuffix + +# Generate the config file +config=$(cat << EOL +{ + "l1StartingBlockTag": "$blockhash", + + "l1ChainID": $L1_CHAIN_ID, + "l2ChainID": $L2_CHAIN_ID, + "l2BlockTime": $L2_BLOCK_TIME, + "l1BlockTime": $L1_BLOCK_TIME, + + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + + "p2pSequencerAddress": "$GS_SEQUENCER_ADDRESS", + "batchInboxAddress": "$batchInboxAddress", + "batchSenderAddress": "$GS_BATCHER_ADDRESS", + + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": $timestamp, + + "l2OutputOracleProposer": "$GS_PROPOSER_ADDRESS", + "l2OutputOracleChallenger": "$GS_ADMIN_ADDRESS", + + "finalizationPeriodSeconds": 12, + + "proxyAdminOwner": "$GS_ADMIN_ADDRESS", + "baseFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "l1FeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "sequencerFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "finalSystemOwner": "$GS_ADMIN_ADDRESS", + "superchainConfigGuardian": "$GS_ADMIN_ADDRESS", + + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + + "gasPriceOracleOverhead": 0, + "gasPriceOracleScalar": 1000000, + + "deployCeloContracts": $DEPLOY_CELO_CONTRACTS, + + "enableGovernance": $ENABLE_GOVERNANCE, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "$GS_ADMIN_ADDRESS", + + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "l2GenesisRegolithTimeOffset": "0x0", + + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250, + "eip1559Elasticity": 6, + + "l2GenesisFjordTimeOffset": "0x0", + "l2GenesisEcotoneTimeOffset": "0x0", + "l2GenesisDeltaTimeOffset": "0x0", + "l2GenesisCanyonTimeOffset": "0x0", + + "systemConfigStartBlock": 0, + + "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + + "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", + "faultGameMaxDepth": 44, + "faultGameClockExtension": 0, + "faultGameMaxClockDuration": 600, + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "faultGameSplitDepth": 14, + "faultGameWithdrawalDelay": 604800, + + "preimageOracleMinProposalSize": 1800000, + "preimageOracleChallengePeriod": 86400, + + "fundDevAccounts": $FUNDS_DEV_ACCOUNTS, + "useFaultProofs": false, + "proofMaturityDelaySeconds": 604800, + "disputeGameFinalityDelaySeconds": 302400, + "respectedGameType": 0, + + "usePlasma": $USE_PLASMA, + "daCommitmentType": "GenericCommitment", + "daChallengeWindow": 1, + "daResolveWindow": 1, + + "useCustomGasToken": $USE_CUSTOM_GAS_TOKEN, + "customGasTokenAddress": "$CUSTOM_GAS_TOKEN_ADDRESS" +} +EOL +) + +# Write the config file + +echo "$config" > deploy-config/"$DEPLOYMENT_CONTEXT".json +echo "Created file deploy-config/$DEPLOYMENT_CONTEXT.json successfully."
diff --git OP/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh CELO/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh new file mode 100755 index 0000000000000000000000000000000000000000..2da9ac5039342a1a694c71955fa2409e7a2eab38 --- /dev/null +++ CELO/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# This script is used to generate the getting-started.json configuration file +# used in the Getting Started quickstart guide on the docs site. Avoids the +# need to have the getting-started.json committed to the repo since it's an +# invalid JSON file when not filled in, which is annoying. + +reqenv() { + if [ -z "${!1}" ]; then + echo "Error: environment variable '$1' is undefined" + exit 1 + fi +} + +# Check required environment variables +reqenv "GS_ADMIN_ADDRESS" +reqenv "GS_BATCHER_ADDRESS" +reqenv "GS_PROPOSER_ADDRESS" +reqenv "GS_SEQUENCER_ADDRESS" +reqenv "L1_RPC_URL" + +# Get the finalized block timestamp and hash +block=$(cast block finalized --rpc-url "$L1_RPC_URL") +timestamp=$(echo "$block" | awk '/timestamp/ { print $2 }') +blockhash=$(echo "$block" | awk '/hash/ { print $2 }') + +# Generate the config file +config=$(cat << EOL +{ + "l1StartingBlockTag": "$blockhash", + + "l1ChainID": $L1_CHAIN_ID, + "l2ChainID": $L2_CHAIN_ID, + "l2BlockTime": 2, + "l1BlockTime": 12, + + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + + "p2pSequencerAddress": "$GS_SEQUENCER_ADDRESS", + "batchInboxAddress": "0xff00000000000000000000000000000000042069", + "batchSenderAddress": "$GS_BATCHER_ADDRESS", + + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": $timestamp, + + "l2OutputOracleProposer": "$GS_PROPOSER_ADDRESS", + "l2OutputOracleChallenger": "$GS_ADMIN_ADDRESS", + + "finalizationPeriodSeconds": 12, + + "proxyAdminOwner": "$GS_ADMIN_ADDRESS", + "baseFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "l1FeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "sequencerFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "finalSystemOwner": "$GS_ADMIN_ADDRESS", + "superchainConfigGuardian": "$GS_ADMIN_ADDRESS", + + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + + "gasPriceOracleOverhead": 2100, + "gasPriceOracleScalar": 1000000, + + "enableGovernance": true, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "$GS_ADMIN_ADDRESS", + + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "l2GenesisRegolithTimeOffset": "0x0", + + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250, + "eip1559Elasticity": 6, + + "l2GenesisDeltaTimeOffset": null, + "l2GenesisCanyonTimeOffset": "0x0", + + "systemConfigStartBlock": 0, + + "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + + "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", + "faultGameMaxDepth": 44, + "faultGameMaxDuration": 1200, + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "faultGameSplitDepth": 14, + + "preimageOracleMinProposalSize": 1800000, + "preimageOracleChallengePeriod": 86400 +} +EOL +) + +# Write the config file +echo "$config" > deploy-config/"$DEPLOYMENT_CONTEXT".json
diff --git OP/packages/contracts-bedrock/scripts/libraries/Process.sol CELO/packages/contracts-bedrock/scripts/libraries/Process.sol index c95a95d76c24fbbba01aafbf6204ef173a6e6f4d..d2cf5c3af4aa04bb69fb6801e63e3ce3fe569d16 100644 --- OP/packages/contracts-bedrock/scripts/libraries/Process.sol +++ CELO/packages/contracts-bedrock/scripts/libraries/Process.sol @@ -10,14 +10,27 @@ /// @notice Foundry cheatcode VM. Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));   - function run(string[] memory cmd) internal returns (bytes memory stdout_) { - Vm.FfiResult memory result = vm.tryFfi(cmd); + /// @notice Run a command in a subprocess. Fails if no output is returned. + /// @param _command Command to run. + function run(string[] memory _command) internal returns (bytes memory stdout_) { + stdout_ = run({ _command: _command, _allowEmpty: false }); + } + + /// @notice Run a command in a subprocess. + /// @param _command Command to run. + /// @param _allowEmpty Allow empty output. + function run(string[] memory _command, bool _allowEmpty) internal returns (bytes memory stdout_) { + Vm.FfiResult memory result = vm.tryFfi(_command); + string memory command; + for (uint256 i = 0; i < _command.length; i++) { + command = string.concat(command, _command[i], " "); + } if (result.exitCode != 0) { - string memory command; - for (uint256 i = 0; i < cmd.length; i++) { - command = string.concat(command, cmd[i], " "); - } revert FfiFailed(string.concat("Command: ", command, "\nError: ", string(result.stderr))); + } + // If the output is empty, result.stdout is "[]". + if (!_allowEmpty && keccak256(result.stdout) == keccak256(bytes("[]"))) { + revert FfiFailed(string.concat("No output from Command: ", command)); } stdout_ = result.stdout; }
diff --git OP/packages/contracts-bedrock/scripts/statediff.sh CELO/packages/contracts-bedrock/scripts/statediff.sh index fa1aef11c88455cd79a2a6d59a2bdb093d812ee4..cce1138c962b66b347d037fa29f2045ac0aa65b1 100755 --- OP/packages/contracts-bedrock/scripts/statediff.sh +++ CELO/packages/contracts-bedrock/scripts/statediff.sh @@ -2,4 +2,4 @@ #!/usr/bin/env bash set -euo pipefail   echo "> Deploying contracts to generate state diff (non-broadcast)" -forge script -vvv scripts/Deploy.s.sol:Deploy --sig 'runWithStateDiff()' +forge script -vvv scripts/deploy/Deploy.s.sol:Deploy --sig 'runWithStateDiff()'
diff --git OP/packages/contracts-bedrock/semver-lock.json CELO/packages/contracts-bedrock/semver-lock.json index a98510e2dfcd51a02a525b2d148a973cde7ed1b6..82cd62f620930a2b57aa49634e599b219daced54 100644 --- OP/packages/contracts-bedrock/semver-lock.json +++ CELO/packages/contracts-bedrock/semver-lock.json @@ -32,15 +32,15 @@ "initCodeHash": "0x14c3a582ca46ef2a6abad5590323f4de26ff4de54415c927c62e131ccbf8d9ba", "sourceCodeHash": "0xf5fcf570721e25459fadbb37e02f9efe349e1c8afcbf1e3b5fdb09c9f612cdc0" }, "src/L1/OptimismPortal.sol": { - "initCodeHash": "0xfdc8cf0b0b26961f6ac493ee564761716447d263291bea4d366a7b94afe33392", - "sourceCodeHash": "0x9fe0a9001edecd2a04daada4ca9e17d66141b1c982f73653493b4703d2c675c4" + "initCodeHash": "0x8f4ca1fa25ecc4602266e5f5eae6daa87afe8595c9d6b3e7e9e37cbe8c5643ed", + "sourceCodeHash": "0xd473cd0debf4a66f54e0932b6d260699e7b5eb55633b70f3e00dc2b1ad6c3a1d" }, "src/L1/OptimismPortal2.sol": { "initCodeHash": "0x45cae622788a795c2fc4f4bc8e6b85d8edf284a1dc20e1b5fa01e88d737deb23", "sourceCodeHash": "0xea564dbff9831ad1bf0c1b345fbc3da4675cf112d2605ba94e1ef5c7b745b7ae" }, "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0x4ab4c99bd776d1817f7475161db0ce47e735a91bb9fb486338238aa762fe0909", + "initCodeHash": "0xa9b58f5e59e662dadfb8335c91f16761cc24d6f6427c1816a7413f771ebbcc2a", "sourceCodeHash": "0x49ae32c402536774928116b833e2256741dbbdf99900ea5df159efab684d1008" }, "src/L1/ProtocolVersions.sol": { @@ -64,8 +64,8 @@ "initCodeHash": "0x623bf6892f0bdb536f2916bc9eb45e52012ad2c80893ff87d750757966b9be68", "sourceCodeHash": "0x3a725791a0f5ed84dc46dcdae26f6170a759b2fe3dc360d704356d088b76cfd6" }, "src/L2/CrossL2Inbox.sol": { - "initCodeHash": "0x46e15ac5de81ea415061d049730da25acf31040d6d5d70fe3a9bf4cac100c282", - "sourceCodeHash": "0xc3d38bfa73fc33369891a2e8c987baf64b1e94c53d6104676fd4c93e1f5c8011" + "initCodeHash": "0x074af4b17cfdd1d1dafaaccb79d68ab4ceef50d35dc205aeeedc265e11ae2a92", + "sourceCodeHash": "0x5b4355b060e8e5ab81047e5f3d093869c2be7bae14a48a0e5ddf6872a219faf2" }, "src/L2/GasPriceOracle.sol": { "initCodeHash": "0xb16f1e370e58c7693fd113a21a1b1e7ccebc03d4f1e5a76786fc27847ef51ead", @@ -100,8 +100,8 @@ "initCodeHash": "0x08bbede75cd6dfd076903b8f04d24f82fa7881576c135825098778632e37eebc", "sourceCodeHash": "0x8388b9b8075f31d580fed815b66b45394e40fb1a63cd8cda2272d2c390fc908c" }, "src/L2/L2ToL2CrossDomainMessenger.sol": { - "initCodeHash": "0x975a4b620e71a1cacd5078972c5e042d010b01e52d0ccd17934cbc7c9890f23b", - "sourceCodeHash": "0x249218d69909750f5245a42d247a789f1837c24863bded94dc577fcbec914175" + "initCodeHash": "0x15fbb6175eb98a7d7c6b99862de49e8c3f8ac768c656e82ad7c41c0d1739bd66", + "sourceCodeHash": "0x1f14aafab2cb15970cccedb461b72218fca8afa6ffd0ac696a9e28ff1415a068" }, "src/L2/SequencerFeeVault.sol": { "initCodeHash": "0xb94145f571e92ee615c6fe903b6568e8aac5fe760b6b65148ffc45d2fb0f5433", @@ -124,8 +124,8 @@ "initCodeHash": "0xde144889fe7d98dbf300a98f5331edd535086a4af8ae6d88ca190c7f4c754a2d", "sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f" }, "src/cannon/MIPS.sol": { - "initCodeHash": "0x1c5dbe83af31e70feb906e2bda2bb1d78d3d15012ec6b11ba5643785657af2a6", - "sourceCodeHash": "0x9bdc97ff4e51fdec7c3e2113d5b60cd64eeb121a51122bea972789d4a5ac3dfa" + "initCodeHash": "0xe9183ee3b69d9ec9594d6b3923d78c86c996cd738ccbc09675bb281284c060af", + "sourceCodeHash": "0x7c2eab73da8b2eeadba30eadb39f20e91307bc29218938fadfc5f73fadcf13bc" }, "src/cannon/PreimageOracle.sol": { "initCodeHash": "0xe5db668fe41436f53995e910488c7c140766ba8745e19743773ebab508efd090", @@ -176,11 +176,11 @@ "initCodeHash": "0xefc67e1be541adfc92f9a5bef36746477299f5e76a4601c12f802af52fb02253", "sourceCodeHash": "0x323f707d4cebc38f59f9241098a1d7e5e790ffcaf1719065edabf4cb794ac745" }, "src/universal/OptimismMintableERC20.sol": { - "initCodeHash": "0x7c6e1cf86cf8622d8beceafa3610ff88eceb3b0fafff0491bfa26a7b876c4d9a", - "sourceCodeHash": "0x52737b23e99bf79dd2c23196b3298e80aa41f740efc6adc7916e696833eb546a" + "initCodeHash": "0x9b18a1ae827de2c28d3b4f92d9fc718889f23f37fd973cf07ea31b93b8f71d87", + "sourceCodeHash": "0xd6bff526d242cb4f0fee0b22da38e51fa5a1de1896c6a9c332079f993b06b0da" }, "src/universal/OptimismMintableERC20Factory.sol": { - "initCodeHash": "0xf6f522681e7ae940cb778db68004f122b25194296a65bba7ad1d792bd593c4a6", + "initCodeHash": "0xf433cfb2b9a65b29c1dd9b6a724bddd3c7bb64e49273492d5bd52e7cb424c4e2", "sourceCodeHash": "0x9b8c73ea139f13028008eedef53a6b07576cd6b08979574e6dde3192656e9268" }, "src/universal/OptimismMintableERC721.sol": {
diff --git OP/packages/contracts-bedrock/snapshots/abi/CalledByVm.json CELO/packages/contracts-bedrock/snapshots/abi/CalledByVm.json new file mode 100644 index 0000000000000000000000000000000000000000..0637a088a01e8ddab3bf3fa98dbe804cbde1a0dc --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/CalledByVm.json @@ -0,0 +1 @@ +[] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json CELO/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json new file mode 100644 index 0000000000000000000000000000000000000000..1f095b33d3bb0171ba35dfdc18e2e5361b4f3e5a --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json @@ -0,0 +1,247 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + } + ], + "name": "getAddressFor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + } + ], + "name": "getAddressForOrDie", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + } + ], + "name": "getAddressForString", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + } + ], + "name": "getAddressForStringOrDie", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "identifierHashes", + "type": "bytes32[]" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "isOneOf", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "registry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setAddressFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "identifier", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "RegistryUpdated", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/CeloTokenL1.json CELO/packages/contracts-bedrock/snapshots/abi/CeloTokenL1.json new file mode 100644 index 0000000000000000000000000000000000000000..ea1de6d1e20933c8e19576668fc8db6e98387cbd --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/CeloTokenL1.json @@ -0,0 +1,298 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "portalProxyAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json CELO/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json new file mode 100644 index 0000000000000000000000000000000000000000..4bdf6bbac31f355f29ff9bfdcda463d77a7b6d8e --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json @@ -0,0 +1,354 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json CELO/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json new file mode 100644 index 0000000000000000000000000000000000000000..4c4ccb64968e89d61bc9081c7cfedc3c8bc471f4 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json @@ -0,0 +1,246 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "currencies", + "outputs": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrencies", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getCurrencyConfig", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "internalType": "struct IFeeCurrencyDirectory.CurrencyConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "removeCurrencies", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "name": "setCurrencyConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/FeeHandler.json CELO/packages/contracts-bedrock/snapshots/abi/FeeHandler.json new file mode 100644 index 0000000000000000000000000000000000000000..a584a53f686d0c6681b11a0bdbe857415bdc3774 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/FeeHandler.json @@ -0,0 +1,813 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [], + "name": "FIXED1_UINT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_BURN", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "activateToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "addToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "burnCelo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "burnFraction", + "outputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "celoToBeBurned", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountToBurn", + "type": "uint256" + } + ], + "name": "dailySellLimitHit", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "deactivateToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "distribute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "distributeAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeBeneficiary", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getPastBurnForToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenCurrentDaySellLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenDailySellLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenHandler", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenMaxSlippage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenToDistribute", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "handle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "handleAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "newFeeBeneficiary", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newBurnFraction", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "handlers", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newLimits", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "newMaxSlippages", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastLimitDay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "removeToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "sell", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fraction", + "type": "uint256" + } + ], + "name": "setBurnFraction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newLimit", + "type": "uint256" + } + ], + "name": "setDailySellLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "setFeeBeneficiary", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "setHandler", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMax", + "type": "uint256" + } + ], + "name": "setMaxSplippage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "fraction", + "type": "uint256" + } + ], + "name": "BurnFractionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "burning", + "type": "uint256" + } + ], + "name": "DailyLimitHit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLimit", + "type": "uint256" + } + ], + "name": "DailyLimitSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DailySellLimitUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newBeneficiary", + "type": "address" + } + ], + "name": "FeeBeneficiarySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "MaxSlippageSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SoldAndBurnedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "TokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "TokenRemoved", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/Freezable.json CELO/packages/contracts-bedrock/snapshots/abi/Freezable.json new file mode 100644 index 0000000000000000000000000000000000000000..dc8fa7e0f21ca37add36efa01627337c9521293c --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/Freezable.json @@ -0,0 +1,93 @@ +[ + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/GoldToken.json CELO/packages/contracts-bedrock/snapshots/abi/GoldToken.json new file mode 100644 index 0000000000000000000000000000000000000000..a52ef10b6a528ba8b47235bebe82741ce20d5640 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/GoldToken.json @@ -0,0 +1,552 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "circulatingSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getBurnedAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "increaseSupply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "transferWithComment", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "TransferComment", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/Initializable.json CELO/packages/contracts-bedrock/snapshots/abi/Initializable.json new file mode 100644 index 0000000000000000000000000000000000000000..aeef476ab67fdf303022548658b887d36bf6f042 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/Initializable.json @@ -0,0 +1,26 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "testingDeployment", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json CELO/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json new file mode 100644 index 0000000000000000000000000000000000000000..7190d528858e5ac8ec5feae77b968b6afaca1d0e --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json @@ -0,0 +1,350 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "midPriceNumerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "midPriceDenominator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "calculateMinAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "tokenAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newMininumReports", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "minimumReports", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sellTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "buyTokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "sell", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMininumReports", + "type": "uint256" + } + ], + "name": "setMinimumReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "minimumReports", + "type": "uint256" + } + ], + "name": "MinimumReportsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "soldTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "boughtTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokenSold", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json CELO/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json new file mode 100644 index 0000000000000000000000000000000000000000..f56f9b579aa578b992b3f8960a77db6c768cbac9 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json @@ -0,0 +1,249 @@ +[ + { + "inputs": [], + "name": "DENOMINATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "expired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isOldestReportExpired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "medianTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "numRates", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "numerators", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + } + ], + "name": "setMedianRate", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "setMedianTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "setMedianTimestampToNow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "rate", + "type": "uint256" + } + ], + "name": "setNumRates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "setOldestReportExpired", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json CELO/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json index 3c6f5e9ab34802a95d672b4b5a34ca3f431645f9..8ced7535ac1dea765bb9709a1be28c9e3824547f 100644 --- OP/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json +++ CELO/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json @@ -155,6 +155,90 @@ "stateMutability": "nonpayable", "type": "function" }, { + "inputs": [ + { + "internalType": "address[]", + "name": "recipients", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [], "name": "decimals", "outputs": [
diff --git OP/packages/contracts-bedrock/snapshots/abi/OptimismPortal.json CELO/packages/contracts-bedrock/snapshots/abi/OptimismPortal.json index 88531b87da44aeb6fed56721470412df51bb343c..ce0c1d8c629ff673bf4c2df9533c1d02255acef7 100644 --- OP/packages/contracts-bedrock/snapshots/abi/OptimismPortal.json +++ CELO/packages/contracts-bedrock/snapshots/abi/OptimismPortal.json @@ -192,6 +192,11 @@ { "internalType": "contract SuperchainConfig", "name": "_superchainConfig", "type": "address" + }, + { + "internalType": "uint256", + "name": "_initialBalance", + "type": "uint256" } ], "name": "initialize",
diff --git OP/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json CELO/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json index 36ed78b23eaddb5847a817869763eaff857d79eb..e24b7bfe031a47ae6756783ee431e47cb0d1e855 100644 --- OP/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json +++ CELO/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json @@ -187,6 +187,11 @@ { "internalType": "contract SuperchainConfig", "name": "_superchainConfig", "type": "address" + }, + { + "internalType": "uint256", + "name": "_initialBalance", + "type": "uint256" } ], "name": "initialize",
diff --git OP/packages/contracts-bedrock/snapshots/abi/SortedOracles.json CELO/packages/contracts-bedrock/snapshots/abi/SortedOracles.json new file mode 100644 index 0000000000000000000000000000000000000000..12a253c5c08be2f7b7d727d99d56c5499b5ed43a --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/SortedOracles.json @@ -0,0 +1,832 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "addOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "breakerBox", + "outputs": [ + { + "internalType": "contract IBreakerBox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "deleteEquivalentToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "equivalentTokens", + "outputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getEquivalentToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getOracles", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getRates", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "enum SortedLinkedListWithMedian.MedianRelation[]", + "name": "", + "type": "uint8[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTimestamps", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "enum SortedLinkedListWithMedian.MedianRelation[]", + "name": "", + "type": "uint8[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTokenReportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isOldestReportExpired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOracle", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRateWithoutEquivalentMapping", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "numRates", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "numTimestamps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "oracles", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "n", + "type": "uint256" + } + ], + "name": "removeExpiredReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "removeOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "lesserKey", + "type": "address" + }, + { + "internalType": "address", + "name": "greaterKey", + "type": "address" + } + ], + "name": "report", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IBreakerBox", + "name": "newBreakerBox", + "type": "address" + } + ], + "name": "setBreakerBox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "equivalentToken", + "type": "address" + } + ], + "name": "setEquivalentToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "setReportExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "setTokenReportExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenReportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newBreakerBox", + "type": "address" + } + ], + "name": "BreakerBoxUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "equivalentToken", + "type": "address" + } + ], + "name": "EquivalentTokenSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "MedianUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "OracleAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "OracleRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "OracleReportRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "OracleReported", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "reportExpiry", + "type": "uint256" + } + ], + "name": "ReportExpirySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportExpiry", + "type": "uint256" + } + ], + "name": "TokenReportExpirySet", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json CELO/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json new file mode 100644 index 0000000000000000000000000000000000000000..693b960cea99c0c7b6d9ab45341f3f56eda21853 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json @@ -0,0 +1,742 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "disable", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "broker", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "gatewayFeeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gatewayFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "exchange", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "address[]", + "name": "initialBalanceAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "initialBalanceValues", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_broker", + "type": "address" + }, + { + "internalType": "address", + "name": "_validators", + "type": "address" + }, + { + "internalType": "address", + "name": "_exchange", + "type": "address" + } + ], + "name": "initializeV2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_broker", + "type": "address" + } + ], + "name": "setBroker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_exchange", + "type": "address" + } + ], + "name": "setExchange", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_validators", + "type": "address" + } + ], + "name": "setValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "transferWithComment", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "validators", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "broker", + "type": "address" + } + ], + "name": "BrokerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "name": "ExchangeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "TransferComment", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "validators", + "type": "address" + } + ], + "name": "ValidatorsUpdated", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json CELO/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json new file mode 100644 index 0000000000000000000000000000000000000000..19c31c979af28ecd9ffcfcda9095f993e30b3ed1 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json @@ -0,0 +1,481 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "midPriceNumerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "midPriceDenominator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "calculateMinAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getRoutersForToken", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "tokenAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newMininumReports", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "minimumReports", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "removeRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sellTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "buyTokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "sell", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMininumReports", + "type": "uint256" + } + ], + "name": "setMinimumReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "minimumReports", + "type": "uint256" + } + ], + "name": "MinimumReportsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "tokneAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "quote", + "type": "uint256" + } + ], + "name": "ReceivedQuote", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterAddressRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterAddressSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterUsed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "soldTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "boughtTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokenSold", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json CELO/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json new file mode 100644 index 0000000000000000000000000000000000000000..dc8fa7e0f21ca37add36efa01627337c9521293c --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json @@ -0,0 +1,93 @@ +[ + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json CELO/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json new file mode 100644 index 0000000000000000000000000000000000000000..0637a088a01e8ddab3bf3fa98dbe804cbde1a0dc --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json @@ -0,0 +1 @@ +[] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json CELO/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json new file mode 100644 index 0000000000000000000000000000000000000000..17b0df2bd7f9e8254e7ac4730d34917b70b3063b --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json @@ -0,0 +1,23 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "32", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "mapping(bytes32 => address)" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1.json CELO/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1.json new file mode 100644 index 0000000000000000000000000000000000000000..e3c218589ca31c3e34d65496c25c72a83d999397 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1.json @@ -0,0 +1,65 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "54", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "55", + "type": "string" + }, + { + "bytes": "1440", + "label": "__gap", + "offset": 0, + "slot": "56", + "type": "uint256[45]" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json CELO/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json new file mode 100644 index 0000000000000000000000000000000000000000..418a98546cf776bc340d3ebdd5775412a3e7986e --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "0", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "1", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "3", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "4", + "type": "string" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json CELO/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json new file mode 100644 index 0000000000000000000000000000000000000000..61ccdc5fb15116df778992284198adbb9aeaa26b --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 1, + "slot": "0", + "type": "address" + }, + { + "bytes": "32", + "label": "currencies", + "offset": 0, + "slot": "1", + "type": "mapping(address => struct IFeeCurrencyDirectory.CurrencyConfig)" + }, + { + "bytes": "32", + "label": "currencyList", + "offset": 0, + "slot": "2", + "type": "address[]" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json CELO/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json new file mode 100644 index 0000000000000000000000000000000000000000..468bb7dc389218cc2a62ad57d94c340b31fa5a30 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json @@ -0,0 +1,72 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "_status", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "lastLimitDay", + "offset": 0, + "slot": "3", + "type": "uint256" + }, + { + "bytes": "32", + "label": "burnFraction", + "offset": 0, + "slot": "4", + "type": "struct FixidityLib.Fraction" + }, + { + "bytes": "20", + "label": "feeBeneficiary", + "offset": 0, + "slot": "5", + "type": "address" + }, + { + "bytes": "32", + "label": "celoToBeBurned", + "offset": 0, + "slot": "6", + "type": "uint256" + }, + { + "bytes": "32", + "label": "tokenStates", + "offset": 0, + "slot": "7", + "type": "mapping(address => struct FeeHandler.TokenState)" + }, + { + "bytes": "64", + "label": "activeTokens", + "offset": 0, + "slot": "8", + "type": "struct EnumerableSet.AddressSet" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json CELO/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json new file mode 100644 index 0000000000000000000000000000000000000000..fb89bbc7e1ab3904137e39358de306a828c60dac --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json CELO/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json new file mode 100644 index 0000000000000000000000000000000000000000..67b349856d86cdaab5dd67f9e9e413210d44ce63 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 1, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "totalSupply_", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "allowed", + "offset": 0, + "slot": "3", + "type": "mapping(address => mapping(address => uint256))" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json CELO/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json new file mode 100644 index 0000000000000000000000000000000000000000..b29972a4de8eb134c79b8e19e36619de89bfeb4b --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json CELO/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json new file mode 100644 index 0000000000000000000000000000000000000000..a66c44056e6d0350f83d4ee520bafeda4d5c2a58 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "minimumReports", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json CELO/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json new file mode 100644 index 0000000000000000000000000000000000000000..c44ef116af9505417a194688daf746a4c58cdcff --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "32", + "label": "numerators", + "offset": 0, + "slot": "0", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "medianTimestamp", + "offset": 0, + "slot": "1", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "numRates", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "expired", + "offset": 0, + "slot": "3", + "type": "mapping(address => bool)" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json CELO/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json new file mode 100644 index 0000000000000000000000000000000000000000..e1e5e1736aff6530fc2e9dbeeecc4a4c9a316365 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json @@ -0,0 +1,72 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "32", + "label": "rates", + "offset": 0, + "slot": "1", + "type": "mapping(address => struct SortedLinkedListWithMedian.List)" + }, + { + "bytes": "32", + "label": "timestamps", + "offset": 0, + "slot": "2", + "type": "mapping(address => struct SortedLinkedListWithMedian.List)" + }, + { + "bytes": "32", + "label": "isOracle", + "offset": 0, + "slot": "3", + "type": "mapping(address => mapping(address => bool))" + }, + { + "bytes": "32", + "label": "oracles", + "offset": 0, + "slot": "4", + "type": "mapping(address => address[])" + }, + { + "bytes": "32", + "label": "reportExpirySeconds", + "offset": 0, + "slot": "5", + "type": "uint256" + }, + { + "bytes": "32", + "label": "tokenReportExpirySeconds", + "offset": 0, + "slot": "6", + "type": "mapping(address => uint256)" + }, + { + "bytes": "20", + "label": "breakerBox", + "offset": 0, + "slot": "7", + "type": "contract IBreakerBox" + }, + { + "bytes": "32", + "label": "equivalentTokens", + "offset": 0, + "slot": "8", + "type": "mapping(address => struct SortedOracles.EquivalentToken)" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json CELO/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json new file mode 100644 index 0000000000000000000000000000000000000000..eea3cafe6e9025cb532486b1e9ff84f4246310ec --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json @@ -0,0 +1,142 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "54", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "55", + "type": "string" + }, + { + "bytes": "1440", + "label": "__gap", + "offset": 0, + "slot": "56", + "type": "uint256[45]" + }, + { + "bytes": "32", + "label": "_HASHED_NAME", + "offset": 0, + "slot": "101", + "type": "bytes32" + }, + { + "bytes": "32", + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "102", + "type": "bytes32" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "103", + "type": "uint256[50]" + }, + { + "bytes": "32", + "label": "_nonces", + "offset": 0, + "slot": "153", + "type": "mapping(address => struct CountersUpgradeable.Counter)" + }, + { + "bytes": "32", + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "154", + "type": "bytes32" + }, + { + "bytes": "1568", + "label": "__gap", + "offset": 0, + "slot": "155", + "type": "uint256[49]" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "204", + "type": "address" + }, + { + "bytes": "1568", + "label": "__gap", + "offset": 0, + "slot": "205", + "type": "uint256[49]" + }, + { + "bytes": "20", + "label": "validators", + "offset": 0, + "slot": "254", + "type": "address" + }, + { + "bytes": "20", + "label": "broker", + "offset": 0, + "slot": "255", + "type": "address" + }, + { + "bytes": "20", + "label": "exchange", + "offset": 0, + "slot": "256", + "type": "address" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json CELO/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json new file mode 100644 index 0000000000000000000000000000000000000000..3688a3204dec12dbace7b35435f8d85cb1c9acb3 --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "minimumReports", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "routerAddresses", + "offset": 0, + "slot": "3", + "type": "mapping(address => struct EnumerableSet.AddressSet)" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json CELO/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json new file mode 100644 index 0000000000000000000000000000000000000000..fb89bbc7e1ab3904137e39358de306a828c60dac --- /dev/null +++ CELO/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + } +] \ No newline at end of file
diff --git OP/packages/contracts-bedrock/src/L1/OptimismPortal.sol CELO/packages/contracts-bedrock/src/L1/OptimismPortal.sol index 38c638aa8e48d8485d1ca17241256aff642b2701..132e60592cb1f72fd17d331798d248afd6da4105 100644 --- OP/packages/contracts-bedrock/src/L1/OptimismPortal.sol +++ CELO/packages/contracts-bedrock/src/L1/OptimismPortal.sol @@ -138,7 +138,8 @@ constructor() { initialize({ _l2Oracle: L2OutputOracle(address(0)), _systemConfig: SystemConfig(address(0)), - _superchainConfig: SuperchainConfig(address(0)) + _superchainConfig: SuperchainConfig(address(0)), + _initialBalance: 0 }); }   @@ -146,10 +147,12 @@ /// @notice Initializer. /// @param _l2Oracle Contract of the L2OutputOracle. /// @param _systemConfig Contract of the SystemConfig. /// @param _superchainConfig Contract of the SuperchainConfig. + /// @param _initialBalance the initial balance assigned to the portal without using deposit txs. function initialize( L2OutputOracle _l2Oracle, SystemConfig _systemConfig, - SuperchainConfig _superchainConfig + SuperchainConfig _superchainConfig, + uint256 _initialBalance ) public initializer @@ -160,6 +163,7 @@ superchainConfig = _superchainConfig; if (l2Sender == address(0)) { l2Sender = Constants.DEFAULT_L2_SENDER; } + _balance = _initialBalance; __ResourceMetering_init(); }
diff --git OP/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol CELO/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol index 211fe76729c479b01477041d710ebbbc04e1c2d0..476c3e12e4b3bebc7d8da7ef888fbfc49b7b252c 100644 --- OP/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol +++ CELO/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol @@ -5,6 +5,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { TransientContext, TransientReentrancyAware } from "src/libraries/TransientContext.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { ICrossL2Inbox } from "src/L2/ICrossL2Inbox.sol"; +import { SafeCall } from "src/libraries/SafeCall.sol";   /// @title IDependencySet /// @notice Interface for L1Block with only `isInDependencySet(uint256)` method. @@ -55,8 +56,8 @@ /// Equal to bytes32(uint256(keccak256("crossl2inbox.identifier.chainid")) - 1) bytes32 internal constant CHAINID_SLOT = 0x6e0446e8b5098b8c8193f964f1b567ec3a2bdaeba33d36acb85c1f1d3f92d313;   /// @notice Semantic version. - /// @custom:semver 0.1.0 - string public constant version = "0.1.0"; + /// @custom:semver 1.0.0-beta.1 + string public constant version = "1.0.0-beta.1";   /// @notice Emitted when a cross chain message is being executed. /// @param encodedId Encoded Identifier of the message. @@ -122,7 +123,7 @@ // Store the Identifier in transient storage. _storeIdentifier(_id);   // Call the target account with the message payload. - bool success = _callWithAllGas(_target, _message); + bool success = SafeCall.call(_target, msg.value, _message);   // Revert if the target call failed. if (!success) revert TargetCallFailed(); @@ -138,24 +139,5 @@ TransientContext.set(BLOCK_NUMBER_SLOT, _id.blockNumber); TransientContext.set(LOG_INDEX_SLOT, _id.logIndex); TransientContext.set(TIMESTAMP_SLOT, _id.timestamp); TransientContext.set(CHAINID_SLOT, _id.chainId); - } - - /// @notice Calls the target address with the message payload and all available gas. - /// @param _target Target address to call. - /// @param _message Message payload to call target with. - /// @return _success True if the call was successful, and false otherwise. - function _callWithAllGas(address _target, bytes memory _message) internal returns (bool _success) { - assembly { - _success := - call( - gas(), // gas - _target, // recipient - callvalue(), // ether value - add(_message, 32), // inloc - mload(_message), // inlen - 0, // outloc - 0 // outlen - ) - } } }
diff --git OP/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol CELO/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol index fe851c5523c857551288af95861a1c2de7693e1c..35ccf60ac6d37dbcd812141b7958fdaf148aee91 100644 --- OP/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol +++ CELO/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol @@ -6,6 +6,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { CrossL2Inbox } from "src/L2/CrossL2Inbox.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/IL2ToL2CrossDomainMessenger.sol"; import { ISemver } from "src/universal/ISemver.sol"; +import { SafeCall } from "src/libraries/SafeCall.sol";   /// @notice Thrown when a non-written slot in transient storage is attempted to be read from. error NotEntered(); @@ -59,8 +60,8 @@ /// @notice Current message version identifier. uint16 public constant messageVersion = uint16(0);   /// @notice Semantic version. - /// @custom:semver 0.1.0 - string public constant version = "0.1.0"; + /// @custom:semver 1.0.0-beta.1 + string public constant version = "1.0.0-beta.1";   /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only be present in this /// mapping if it has successfully been relayed on this chain, and can therefore not be relayed again. @@ -175,7 +176,7 @@ }   _storeMessageMetadata(_source, _sender);   - bool success = _callWithAllGas(_target, _message); + bool success = SafeCall.call(_target, msg.value, _message);   if (success) { successfulMessages[messageHash] = true; @@ -211,25 +212,6 @@ function _storeMessageMetadata(uint256 _source, address _sender) internal { assembly { tstore(CROSS_DOMAIN_MESSAGE_SENDER_SLOT, _sender) tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source) - } - } - - /// @notice Calls the target address with the message payload and all available gas. - /// @param _target Target address to call. - /// @param _message Message payload to call target with. - /// @return _success True if the call was successful, and false otherwise. - function _callWithAllGas(address _target, bytes memory _message) internal returns (bool _success) { - assembly { - _success := - call( - gas(), // gas - _target, // recipient - callvalue(), // ether value - add(_message, 32), // inloc - mload(_message), // inlen - 0, // outloc - 0 // outlen - ) } } }
diff --git OP/packages/contracts-bedrock/src/cannon/MIPS.sol CELO/packages/contracts-bedrock/src/cannon/MIPS.sol index 78064149bfaf706bab7a3eca7900093e290af2c5..f53dede0286f3edb8abd41b81d0a7a660962e492 100644 --- OP/packages/contracts-bedrock/src/cannon/MIPS.sol +++ CELO/packages/contracts-bedrock/src/cannon/MIPS.sol @@ -4,7 +4,10 @@ import { ISemver } from "src/universal/ISemver.sol"; import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol"; import { PreimageKeyLib } from "./PreimageKeyLib.sol"; -import "src/cannon/libraries/MIPSInstructions.sol" as ins; +import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol"; +import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; +import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; +import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol";   /// @title MIPS /// @notice The MIPS contract emulates a single MIPS instruction. @@ -45,21 +48,13 @@ uint32 public constant BRK_START = 0x40000000;   /// @notice The semantic version of the MIPS contract. /// @custom:semver 1.0.1 - string public constant version = "1.1.0-beta.1"; - - uint32 internal constant FD_STDIN = 0; - uint32 internal constant FD_STDOUT = 1; - uint32 internal constant FD_STDERR = 2; - uint32 internal constant FD_HINT_READ = 3; - uint32 internal constant FD_HINT_WRITE = 4; - uint32 internal constant FD_PREIMAGE_READ = 5; - uint32 internal constant FD_PREIMAGE_WRITE = 6; - - uint32 internal constant EBADF = 0x9; - uint32 internal constant EINVAL = 0x16; + string public constant version = "1.1.0-beta.4";   /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; + + // The offset of the start of proof calldata (_proof.offset) in the step() function + uint256 internal constant STEP_PROOF_OFFSET = 420;   /// @param _oracle The address of the preimage oracle contract. constructor(IPreimageOracle _oracle) { @@ -147,481 +142,59 @@ assembly { state := 0x80 }   - // Load the syscall number from the registers - uint32 syscall_no = state.registers[2]; + // Load the syscall numbers and args from the registers + (uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2) = sys.getSyscallArgs(state.registers); + uint32 v0 = 0; uint32 v1 = 0;   - // Load the syscall arguments from the registers - uint32 a0 = state.registers[4]; - uint32 a1 = state.registers[5]; - uint32 a2 = state.registers[6]; - - // mmap: Allocates a page from the heap. - if (syscall_no == 4090) { - uint32 sz = a1; - if (sz & 4095 != 0) { - // adjust size to align with page size - sz += 4096 - (sz & 4095); - } - if (a0 == 0) { - v0 = state.heap; - state.heap += sz; - } else { - v0 = a0; - } - } - // brk: Returns a fixed address for the program break at 0x40000000 - else if (syscall_no == 4045) { + if (syscall_no == sys.SYS_MMAP) { + (v0, v1, state.heap) = sys.handleSysMmap(a0, a1, state.heap); + } else if (syscall_no == sys.SYS_BRK) { + // brk: Returns a fixed address for the program break at 0x40000000 v0 = BRK_START; - } - // clone (not supported) returns 1 - else if (syscall_no == 4120) { + } else if (syscall_no == sys.SYS_CLONE) { + // clone (not supported) returns 1 v0 = 1; - } - // exit group: Sets the Exited and ExitCode states to true and argument 0. - else if (syscall_no == 4246) { + } else if (syscall_no == sys.SYS_EXIT_GROUP) { + // exit group: Sets the Exited and ExitCode states to true and argument 0. state.exited = true; state.exitCode = uint8(a0); return outputState(); - } - // read: Like Linux read syscall. Splits unaligned reads into aligned reads. - else if (syscall_no == 4003) { - // args: a0 = fd, a1 = addr, a2 = count - // returns: v0 = read, v1 = err code - if (a0 == FD_STDIN) { - // Leave v0 and v1 zero: read nothing, no error - } - // pre-image oracle read - else if (a0 == FD_PREIMAGE_READ) { - // verify proof 1 is correct, and get the existing memory. - uint32 mem = readMem(a1 & 0xFFffFFfc, 1); // mask the addr to align it to 4 bytes - bytes32 preimageKey = state.preimageKey; - // If the preimage key is a local key, localize it in the context of the caller. - if (uint8(preimageKey[0]) == 1) { - preimageKey = PreimageKeyLib.localize(preimageKey, _localContext); - } - (bytes32 dat, uint256 datLen) = ORACLE.readPreimage(preimageKey, state.preimageOffset); - - // Transform data for writing to memory - // We use assembly for more precise ops, and no var count limit - assembly { - let alignment := and(a1, 3) // the read might not start at an aligned address - let space := sub(4, alignment) // remaining space in memory word - if lt(space, datLen) { datLen := space } // if less space than data, shorten data - if lt(a2, datLen) { datLen := a2 } // if requested to read less, read less - dat := shr(sub(256, mul(datLen, 8)), dat) // right-align data - dat := shl(mul(sub(sub(4, datLen), alignment), 8), dat) // position data to insert into memory - // word - let mask := sub(shl(mul(sub(4, alignment), 8), 1), 1) // mask all bytes after start - let suffixMask := sub(shl(mul(sub(sub(4, alignment), datLen), 8), 1), 1) // mask of all bytes - // starting from end, maybe none - mask := and(mask, not(suffixMask)) // reduce mask to just cover the data we insert - mem := or(and(mem, not(mask)), dat) // clear masked part of original memory, and insert data - } - - // Write memory back - writeMem(a1 & 0xFFffFFfc, 1, mem); - state.preimageOffset += uint32(datLen); - v0 = uint32(datLen); - } - // hint response - else if (a0 == FD_HINT_READ) { - // Don't read into memory, just say we read it all - // The result is ignored anyway - v0 = a2; - } else { - v0 = 0xFFffFFff; - v1 = EBADF; - } - } - // write: like Linux write syscall. Splits unaligned writes into aligned writes. - else if (syscall_no == 4004) { - // args: a0 = fd, a1 = addr, a2 = count - // returns: v0 = written, v1 = err code - if (a0 == FD_STDOUT || a0 == FD_STDERR || a0 == FD_HINT_WRITE) { - v0 = a2; // tell program we have written everything - } - // pre-image oracle - else if (a0 == FD_PREIMAGE_WRITE) { - uint32 mem = readMem(a1 & 0xFFffFFfc, 1); // mask the addr to align it to 4 bytes - bytes32 key = state.preimageKey; - - // Construct pre-image key from memory - // We use assembly for more precise ops, and no var count limit - assembly { - let alignment := and(a1, 3) // the read might not start at an aligned address - let space := sub(4, alignment) // remaining space in memory word - if lt(space, a2) { a2 := space } // if less space than data, shorten data - key := shl(mul(a2, 8), key) // shift key, make space for new info - let mask := sub(shl(mul(a2, 8), 1), 1) // mask for extracting value from memory - mem := and(shr(mul(sub(space, a2), 8), mem), mask) // align value to right, mask it - key := or(key, mem) // insert into key - } - - // Write pre-image key to oracle - state.preimageKey = key; - state.preimageOffset = 0; // reset offset, to read new pre-image data from the start - v0 = a2; - } else { - v0 = 0xFFffFFff; - v1 = EBADF; - } - } - // fcntl: Like linux fcntl syscall, but only supports minimal file-descriptor control commands, - // to retrieve the file-descriptor R/W flags. - else if (syscall_no == 4055) { - // fcntl - // args: a0 = fd, a1 = cmd - if (a1 == 3) { - // F_GETFL: get file descriptor flags - if (a0 == FD_STDIN || a0 == FD_PREIMAGE_READ || a0 == FD_HINT_READ) { - v0 = 0; // O_RDONLY - } else if (a0 == FD_STDOUT || a0 == FD_STDERR || a0 == FD_PREIMAGE_WRITE || a0 == FD_HINT_WRITE) { - v0 = 1; // O_WRONLY - } else { - v0 = 0xFFffFFff; - v1 = EBADF; - } - } else { - v0 = 0xFFffFFff; - v1 = EINVAL; // cmd not recognized by this kernel - } - } - - // Write the results back to the state registers - state.registers[2] = v0; - state.registers[7] = v1; - - // Update the PC and nextPC - state.pc = state.nextPC; - state.nextPC = state.nextPC + 4; - - out_ = outputState(); - } - } - - /// @notice Handles a branch instruction, updating the MIPS state PC where needed. - /// @param _opcode The opcode of the branch instruction. - /// @param _insn The instruction to be executed. - /// @param _rtReg The register to be used for the branch. - /// @param _rs The register to be compared with the branch register. - /// @return out_ The hashed MIPS state. - function handleBranch(uint32 _opcode, uint32 _insn, uint32 _rtReg, uint32 _rs) internal returns (bytes32 out_) { - unchecked { - // Load state from memory - State memory state; - assembly { - state := 0x80 - } - - bool shouldBranch = false; - - if (state.nextPC != state.pc + 4) { - revert("branch in delay slot"); - } - - // beq/bne: Branch on equal / not equal - if (_opcode == 4 || _opcode == 5) { - uint32 rt = state.registers[_rtReg]; - shouldBranch = (_rs == rt && _opcode == 4) || (_rs != rt && _opcode == 5); - } - // blez: Branches if instruction is less than or equal to zero - else if (_opcode == 6) { - shouldBranch = int32(_rs) <= 0; - } - // bgtz: Branches if instruction is greater than zero - else if (_opcode == 7) { - shouldBranch = int32(_rs) > 0; - } - // bltz/bgez: Branch on less than zero / greater than or equal to zero - else if (_opcode == 1) { - // regimm - uint32 rtv = ((_insn >> 16) & 0x1F); - if (rtv == 0) { - shouldBranch = int32(_rs) < 0; - } - if (rtv == 1) { - shouldBranch = int32(_rs) >= 0; - } - } - - // Update the state's previous PC - uint32 prevPC = state.pc; - - // Execute the delay slot first - state.pc = state.nextPC; - - // If we should branch, update the PC to the branch target - // Otherwise, proceed to the next instruction - if (shouldBranch) { - state.nextPC = prevPC + 4 + (ins.signExtend(_insn & 0xFFFF, 16) << 2); - } else { - state.nextPC = state.nextPC + 4; - } - - // Return the hash of the resulting state - out_ = outputState(); - } - } - - /// @notice Handles HI and LO register instructions. - /// @param _func The function code of the instruction. - /// @param _rs The value of the RS register. - /// @param _rt The value of the RT register. - /// @param _storeReg The register to store the result in. - /// @return out_ The hashed MIPS state. - function handleHiLo(uint32 _func, uint32 _rs, uint32 _rt, uint32 _storeReg) internal returns (bytes32 out_) { - unchecked { - // Load state from memory - State memory state; - assembly { - state := 0x80 - } - - uint32 val; - - // mfhi: Move the contents of the HI register into the destination - if (_func == 0x10) { - val = state.hi; - } - // mthi: Move the contents of the source into the HI register - else if (_func == 0x11) { - state.hi = _rs; - } - // mflo: Move the contents of the LO register into the destination - else if (_func == 0x12) { - val = state.lo; - } - // mtlo: Move the contents of the source into the LO register - else if (_func == 0x13) { - state.lo = _rs; - } - // mult: Multiplies `rs` by `rt` and stores the result in HI and LO registers - else if (_func == 0x18) { - uint64 acc = uint64(int64(int32(_rs)) * int64(int32(_rt))); - state.hi = uint32(acc >> 32); - state.lo = uint32(acc); - } - // multu: Unsigned multiplies `rs` by `rt` and stores the result in HI and LO registers - else if (_func == 0x19) { - uint64 acc = uint64(uint64(_rs) * uint64(_rt)); - state.hi = uint32(acc >> 32); - state.lo = uint32(acc); - } - // div: Divides `rs` by `rt`. - // Stores the quotient in LO - // And the remainder in HI - else if (_func == 0x1a) { - if (int32(_rt) == 0) { - revert("MIPS: division by zero"); - } - state.hi = uint32(int32(_rs) % int32(_rt)); - state.lo = uint32(int32(_rs) / int32(_rt)); - } - // divu: Unsigned divides `rs` by `rt`. - // Stores the quotient in LO - // And the remainder in HI - else if (_func == 0x1b) { - if (_rt == 0) { - revert("MIPS: division by zero"); - } - state.hi = _rs % _rt; - state.lo = _rs / _rt; - } - - // Store the result in the destination register, if applicable - if (_storeReg != 0) { - state.registers[_storeReg] = val; - } - - // Update the PC - state.pc = state.nextPC; - state.nextPC = state.nextPC + 4; - - // Return the hash of the resulting state - out_ = outputState(); - } - } - - /// @notice Handles a jump instruction, updating the MIPS state PC where needed. - /// @param _linkReg The register to store the link to the instruction after the delay slot instruction. - /// @param _dest The destination to jump to. - /// @return out_ The hashed MIPS state. - function handleJump(uint32 _linkReg, uint32 _dest) internal returns (bytes32 out_) { - unchecked { - // Load state from memory. - State memory state; - assembly { - state := 0x80 - } - - if (state.nextPC != state.pc + 4) { - revert("jump in delay slot"); + } else if (syscall_no == sys.SYS_READ) { + (v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead({ + _a0: a0, + _a1: a1, + _a2: a2, + _preimageKey: state.preimageKey, + _preimageOffset: state.preimageOffset, + _localContext: _localContext, + _oracle: ORACLE, + _proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), + _memRoot: state.memRoot + }); + } else if (syscall_no == sys.SYS_WRITE) { + (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ + _a0: a0, + _a1: a1, + _a2: a2, + _preimageKey: state.preimageKey, + _preimageOffset: state.preimageOffset, + _proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), + _memRoot: state.memRoot + }); + } else if (syscall_no == sys.SYS_FCNTL) { + (v0, v1) = sys.handleSysFcntl(a0, a1); }   - // Update the next PC to the jump destination. - uint32 prevPC = state.pc; - state.pc = state.nextPC; - state.nextPC = _dest; + st.CpuScalars memory cpu = getCpuScalars(state); + sys.handleSyscallUpdates(cpu, state.registers, v0, v1); + setStateCpuScalars(state, cpu);   - // Update the link-register to the instruction after the delay slot instruction. - if (_linkReg != 0) { - state.registers[_linkReg] = prevPC + 8; - } - - // Return the hash of the resulting state. out_ = outputState(); } }   - /// @notice Handles a storing a value into a register. - /// @param _storeReg The register to store the value into. - /// @param _val The value to store. - /// @param _conditional Whether or not the store is conditional. - /// @return out_ The hashed MIPS state. - function handleRd(uint32 _storeReg, uint32 _val, bool _conditional) internal returns (bytes32 out_) { - unchecked { - // Load state from memory. - State memory state; - assembly { - state := 0x80 - } - - // The destination register must be valid. - require(_storeReg < 32, "valid register"); - - // Never write to reg 0, and it can be conditional (movz, movn). - if (_storeReg != 0 && _conditional) { - state.registers[_storeReg] = _val; - } - - // Update the PC. - state.pc = state.nextPC; - state.nextPC = state.nextPC + 4; - - // Return the hash of the resulting state. - out_ = outputState(); - } - } - - /// @notice Computes the offset of the proof in the calldata. - /// @param _proofIndex The index of the proof in the calldata. - /// @return offset_ The offset of the proof in the calldata. - function proofOffset(uint8 _proofIndex) internal pure returns (uint256 offset_) { - unchecked { - // A proof of 32 bit memory, with 32-byte leaf values, is (32-5)=27 bytes32 entries. - // And the leaf value itself needs to be encoded as well. And proof.offset == 420 - offset_ = 420 + (uint256(_proofIndex) * (28 * 32)); - uint256 s = 0; - assembly { - s := calldatasize() - } - require(s >= (offset_ + 28 * 32), "check that there is enough calldata"); - return offset_; - } - } - - /// @notice Reads a 32-bit value from memory. - /// @param _addr The address to read from. - /// @param _proofIndex The index of the proof in the calldata. - /// @return out_ The hashed MIPS state. - function readMem(uint32 _addr, uint8 _proofIndex) internal pure returns (uint32 out_) { - unchecked { - // Compute the offset of the proof in the calldata. - uint256 offset = proofOffset(_proofIndex); - - assembly { - // Validate the address alignement. - if and(_addr, 3) { revert(0, 0) } - - // Load the leaf value. - let leaf := calldataload(offset) - offset := add(offset, 32) - - // Convenience function to hash two nodes together in scratch space. - function hashPair(a, b) -> h { - mstore(0, a) - mstore(32, b) - h := keccak256(0, 64) - } - - // Start with the leaf node. - // Work back up by combining with siblings, to reconstruct the root. - let path := shr(5, _addr) - let node := leaf - for { let i := 0 } lt(i, 27) { i := add(i, 1) } { - let sibling := calldataload(offset) - offset := add(offset, 32) - switch and(shr(i, path), 1) - case 0 { node := hashPair(node, sibling) } - case 1 { node := hashPair(sibling, node) } - } - - // Load the memory root from the first field of state. - let memRoot := mload(0x80) - - // Verify the root matches. - if iszero(eq(node, memRoot)) { - mstore(0, 0x0badf00d) - revert(0, 32) - } - - // Bits to shift = (32 - 4 - (addr % 32)) * 8 - let shamt := shl(3, sub(sub(32, 4), and(_addr, 31))) - out_ := and(shr(shamt, leaf), 0xFFffFFff) - } - } - } - - /// @notice Writes a 32-bit value to memory. - /// This function first overwrites the part of the leaf. - /// Then it recomputes the memory merkle root. - /// @param _addr The address to write to. - /// @param _proofIndex The index of the proof in the calldata. - /// @param _val The value to write. - function writeMem(uint32 _addr, uint8 _proofIndex, uint32 _val) internal pure { - unchecked { - // Compute the offset of the proof in the calldata. - uint256 offset = proofOffset(_proofIndex); - - assembly { - // Validate the address alignement. - if and(_addr, 3) { revert(0, 0) } - - // Load the leaf value. - let leaf := calldataload(offset) - let shamt := shl(3, sub(sub(32, 4), and(_addr, 31))) - - // Mask out 4 bytes, and OR in the value - leaf := or(and(leaf, not(shl(shamt, 0xFFffFFff))), shl(shamt, _val)) - offset := add(offset, 32) - - // Convenience function to hash two nodes together in scratch space. - function hashPair(a, b) -> h { - mstore(0, a) - mstore(32, b) - h := keccak256(0, 64) - } - - // Start with the leaf node. - // Work back up by combining with siblings, to reconstruct the root. - let path := shr(5, _addr) - let node := leaf - for { let i := 0 } lt(i, 27) { i := add(i, 1) } { - let sibling := calldataload(offset) - offset := add(offset, 32) - switch and(shr(i, path), 1) - case 0 { node := hashPair(node, sibling) } - case 1 { node := hashPair(sibling, node) } - } - - // Store the new memory root in the first field of state. - mstore(0x80, node) - } - } - } - /// @notice Executes a single step of the vm. /// Will revert if any required input state is missing. /// @param _stateData The encoded state witness data. @@ -646,7 +219,7 @@ if iszero(eq(_stateData.offset, 132)) { // 32*4+4=132 expected state data offset revert(0, 0) } - if iszero(eq(_proof.offset, 420)) { + if iszero(eq(_proof.offset, STEP_PROOF_OFFSET)) { // 132+32+256=420 expected proof offset revert(0, 0) } @@ -688,14 +261,15 @@ state.step += 1;   // instruction fetch - uint32 insn = readMem(state.pc, 0); + uint256 insnProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 0); + uint32 insn = MIPSMemory.readMem(state.memRoot, state.pc, insnProofOffset); uint32 opcode = insn >> 26; // 6-bits   // j-type j/jal if (opcode == 2 || opcode == 3) { // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset uint32 target = (state.nextPC & 0xF0000000) | (insn & 0x03FFFFFF) << 2; - return handleJump(opcode == 2 ? 0 : 31, target); + return handleJumpAndReturnOutput(state, opcode == 2 ? 0 : 31, target); }   // register fetch @@ -730,7 +304,19 @@ rdReg = rtReg; }   if ((opcode >= 4 && opcode < 8) || opcode == 1) { - return handleBranch(opcode, insn, rtReg, rs); + st.CpuScalars memory cpu = getCpuScalars(state); + + ins.handleBranch({ + _cpu: cpu, + _registers: state.registers, + _opcode: opcode, + _insn: insn, + _rtReg: rtReg, + _rs: rs + }); + setStateCpuScalars(state, cpu); + + return outputState(); }   uint32 storeAddr = 0xFF_FF_FF_FF; @@ -741,7 +327,8 @@ if (opcode >= 0x20) { // M[R[rs]+SignExtImm] rs += ins.signExtend(insn & 0xFFFF, 16); uint32 addr = rs & 0xFFFFFFFC; - mem = readMem(addr, 1); + uint256 memProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1); + mem = MIPSMemory.readMem(state.memRoot, addr, memProofOffset); if (opcode >= 0x28 && opcode != 0x30) { // store storeAddr = addr; @@ -758,16 +345,16 @@ uint32 func = insn & 0x3f; // 6-bits if (opcode == 0 && func >= 8 && func < 0x1c) { if (func == 8 || func == 9) { // jr/jalr - return handleJump(func == 8 ? 0 : rdReg, rs); + return handleJumpAndReturnOutput(state, func == 8 ? 0 : rdReg, rs); }   if (func == 0xa) { // movz - return handleRd(rdReg, rs, rt == 0); + return handleRdAndReturnOutput(state, rdReg, rs, rt == 0); } if (func == 0xb) { // movn - return handleRd(rdReg, rs, rt != 0); + return handleRdAndReturnOutput(state, rdReg, rs, rt != 0); }   // syscall (can read and write) @@ -778,7 +365,19 @@ // lo and hi registers // can write back if (func >= 0x10 && func < 0x1c) { - return handleHiLo(func, rs, rt, rdReg); + st.CpuScalars memory cpu = getCpuScalars(state); + + ins.handleHiLo({ + _cpu: cpu, + _registers: state.registers, + _func: func, + _rs: rs, + _rt: rt, + _storeReg: rdReg + }); + + setStateCpuScalars(state, cpu); + return outputState(); } }   @@ -789,11 +388,62 @@ }   // write memory if (storeAddr != 0xFF_FF_FF_FF) { - writeMem(storeAddr, 1, val); + uint256 memProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1); + state.memRoot = MIPSMemory.writeMem(storeAddr, memProofOffset, val); }   // write back the value to destination register - return handleRd(rdReg, val, true); + return handleRdAndReturnOutput(state, rdReg, val, true); } + } + + function handleJumpAndReturnOutput( + State memory _state, + uint32 _linkReg, + uint32 _dest + ) + internal + returns (bytes32 out_) + { + st.CpuScalars memory cpu = getCpuScalars(_state); + + ins.handleJump({ _cpu: cpu, _registers: _state.registers, _linkReg: _linkReg, _dest: _dest }); + + setStateCpuScalars(_state, cpu); + return outputState(); + } + + function handleRdAndReturnOutput( + State memory _state, + uint32 _storeReg, + uint32 _val, + bool _conditional + ) + internal + returns (bytes32 out_) + { + st.CpuScalars memory cpu = getCpuScalars(_state); + + ins.handleRd({ + _cpu: cpu, + _registers: _state.registers, + _storeReg: _storeReg, + _val: _val, + _conditional: _conditional + }); + + setStateCpuScalars(_state, cpu); + return outputState(); + } + + function getCpuScalars(State memory _state) internal pure returns (st.CpuScalars memory) { + return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi }); + } + + function setStateCpuScalars(State memory _state, st.CpuScalars memory _cpu) internal pure { + _state.pc = _cpu.pc; + _state.nextPC = _cpu.nextPC; + _state.lo = _cpu.lo; + _state.hi = _cpu.hi; } }
diff --git OP/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol CELO/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol new file mode 100644 index 0000000000000000000000000000000000000000..f67beaaf59a55ff6fc4022443b1179459a8bacbf --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +abstract contract AbstractFeeCurrency is ERC20 { + modifier onlyVm() { + require(msg.sender == address(0), "Only VM can call"); + _; + } + + function debitGasFees(address from, uint256 value) external onlyVm { + _burn(from, value); + } + + // New function signature, will be used when all fee currencies have migrated + function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) public onlyVm { + require(recipients.length == amounts.length, "Recipients and amounts must be the same length."); + + for (uint256 i = 0; i < recipients.length; i++) { + _mint(recipients[i], amounts[i]); + } + } + + // Old function signature for backwards compatibility + function creditGasFees( + address from, + address feeRecipient, + address, // gatewayFeeRecipient, unused + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256, // gatewayFee, unused + uint256 baseTxFee + ) + public + onlyVm + { + // Calling the new creditGasFees would make sense here, but that is not + // possible due to its calldata arguments. + _mint(from, refund); + _mint(feeRecipient, tipTxFee); + _mint(communityFund, baseTxFee); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/CalledByVm.sol CELO/packages/contracts-bedrock/src/celo/CalledByVm.sol new file mode 100644 index 0000000000000000000000000000000000000000..c3f6efe12072ef8c87e213f9c29b0789c26cff0f --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/CalledByVm.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +contract CalledByVm { + modifier onlyVm() { + require(msg.sender == address(0), "Only VM can call"); + _; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/CeloPredeploys.sol CELO/packages/contracts-bedrock/src/celo/CeloPredeploys.sol new file mode 100644 index 0000000000000000000000000000000000000000..7561726fa16c6ae27c2acc3cf42020adec9ee2ac --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/CeloPredeploys.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { console2 as console } from "forge-std/console2.sol"; + +/// @title CeloPredeploys +/// @notice Contains constant addresses for protocol contracts that are pre-deployed to the L2 system. +library CeloPredeploys { + address internal constant CELO_REGISTRY = 0x000000000000000000000000000000000000ce10; + address internal constant GOLD_TOKEN = 0x471EcE3750Da237f93B8E339c536989b8978a438; + address internal constant FEE_HANDLER = 0xcD437749E43A154C07F3553504c68fBfD56B8778; + address internal constant MENTO_FEE_HANDLER_SELLER = 0x4eFa274B7e33476C961065000D58ee09F7921A74; + address internal constant UNISWAP_FEE_HANDLER_SELLER = 0xD3aeE28548Dbb65DF03981f0dC0713BfCBd10a97; + address internal constant SORTED_ORACLES = 0xefB84935239dAcdecF7c5bA76d8dE40b077B7b33; + address internal constant ADDRESS_SORTED_LINKED_LIST_WITH_MEDIAN = 0xED477A99035d0c1e11369F1D7A4e587893cc002B; + address internal constant FEE_CURRENCY = 0x4200000000000000000000000000000000001022; + address internal constant FEE_CURRENCY_DIRECTORY = 0x71FFbD48E34bdD5a87c3c683E866dc63b8B2a685; + address internal constant cUSD = 0x765DE816845861e75A25fCA122bb6898B8B1282a; + + /// @notice Returns the name of the predeploy at the given address. + function getName(address _addr) internal pure returns (string memory out_) { + // require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); + + if (_addr == CELO_REGISTRY) return "CeloRegistry"; + if (_addr == GOLD_TOKEN) return "GoldToken"; + if (_addr == FEE_HANDLER) return "FeeHandler"; + if (_addr == MENTO_FEE_HANDLER_SELLER) return "MentoFeeHandlerSeller"; + if (_addr == UNISWAP_FEE_HANDLER_SELLER) return "UniswapFeeHandlerSeller"; + if (_addr == SORTED_ORACLES) return "SortedOracles"; + if (_addr == ADDRESS_SORTED_LINKED_LIST_WITH_MEDIAN) return "AddressSortedLinkedListWithMedian"; + if (_addr == FEE_CURRENCY) return "FeeCurrency"; + if (_addr == FEE_CURRENCY_DIRECTORY) return "FeeCurrencyDirectory"; + if (_addr == cUSD) return "cUSD"; + + revert("Predeploys: unnamed predeploy"); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/CeloRegistry.sol CELO/packages/contracts-bedrock/src/celo/CeloRegistry.sol new file mode 100644 index 0000000000000000000000000000000000000000..7da4cfb35ddfef5c49183c7c3523f658e071aa33 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/CeloRegistry.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; + +import "./interfaces/ICeloRegistry.sol"; +import "./Initializable.sol"; + +/** + * @title Routes identifiers to addresses. + */ +contract CeloRegistry is ICeloRegistry, Ownable, Initializable { + mapping(bytes32 => address) public registry; + + event RegistryUpdated(string identifier, bytes32 indexed identifierHash, address indexed addr); + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + */ + function initialize() external initializer { + _transferOwnership(msg.sender); + } + + /** + * @notice Associates the given address with the given identifier. + * @param identifier Identifier of contract whose address we want to set. + * @param addr Address of contract. + */ + function setAddressFor(string calldata identifier, address addr) external onlyOwner { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + registry[identifierHash] = addr; + emit RegistryUpdated(identifier, identifierHash, addr); + } + + /** + * @notice Gets address associated with the given identifierHash. + * @param identifierHash Identifier hash of contract whose address we want to look up. + * @dev Throws if address not set. + */ + function getAddressForOrDie(bytes32 identifierHash) external view returns (address) { + require(registry[identifierHash] != address(0), "identifier has no registry entry"); + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifierHash. + * @param identifierHash Identifier hash of contract whose address we want to look up. + */ + function getAddressFor(bytes32 identifierHash) external view returns (address) { + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifier. + * @param identifier Identifier of contract whose address we want to look up. + * @dev Throws if address not set. + */ + function getAddressForStringOrDie(string calldata identifier) external view returns (address) { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + require(registry[identifierHash] != address(0), "identifier has no registry entry"); + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifier. + * @param identifier Identifier of contract whose address we want to look up. + */ + function getAddressForString(string calldata identifier) external view returns (address) { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + return registry[identifierHash]; + } + + /** + * @notice Iterates over provided array of identifiers, getting the address for each. + * Returns true if `sender` matches the address of one of the provided identifiers. + * @param identifierHashes Array of hashes of approved identifiers. + * @param sender Address in question to verify membership. + * @return True if `sender` corresponds to the address of any of `identifiers` + * registry entries. + */ + function isOneOf(bytes32[] calldata identifierHashes, address sender) external view returns (bool) { + for (uint256 i = 0; i < identifierHashes.length; i++) { + if (registry[identifierHashes[i]] == sender) { + return true; + } + } + return false; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/CeloTokenL1.sol CELO/packages/contracts-bedrock/src/celo/CeloTokenL1.sol new file mode 100644 index 0000000000000000000000000000000000000000..8ef3fb5abd9bbf8355d72e95867827c7cb2d257e --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/CeloTokenL1.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +string constant NAME = "Celo native asset"; +string constant SYMBOL = "CELO"; +uint256 constant TOTAL_MARKET_CAP = 1000000000e18; // 1 billion CELO + +contract CeloTokenL1 is ERC20Upgradeable { + function initialize(address portalProxyAddress) public initializer { + __ERC20_init(NAME, SYMBOL); + _mint(portalProxyAddress, TOTAL_MARKET_CAP); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol CELO/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol new file mode 100644 index 0000000000000000000000000000000000000000..21fc7ff3181a15e8d87b7f3ab89f713870197d48 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Initializable.sol"; +import "./interfaces/IOracle.sol"; +import "./interfaces/IFeeCurrencyDirectory.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +contract FeeCurrencyDirectory is IFeeCurrencyDirectory, Initializable, Ownable { + mapping(address => CurrencyConfig) public currencies; + address[] private currencyList; + + constructor(bool test) Initializable(test) { } + + /** + * @notice Initializes the contract with the owner set. + */ + function initialize() public initializer { + _transferOwnership(msg.sender); + } + + /** + * @notice Sets the currency configuration for a token. + * @dev This action can only be performed by the contract owner. + * @param token The token address. + * @param oracle The oracle address for price fetching. + * @param intrinsicGas The intrinsic gas value for transactions. + */ + function setCurrencyConfig(address token, address oracle, uint256 intrinsicGas) external onlyOwner { + require(oracle != address(0), "Oracle address cannot be zero"); + require(intrinsicGas > 0, "Intrinsic gas cannot be zero"); + require(currencies[token].oracle == address(0), "Currency already in the directory"); + + currencies[token] = CurrencyConfig({ oracle: oracle, intrinsicGas: intrinsicGas }); + currencyList.push(token); + } + + /** + * @notice Removes a token from the directory. + * @dev This action can only be performed by the contract owner. + * @param token The token address to remove. + * @param index The index in the list of directory currencies. + */ + function removeCurrencies(address token, uint256 index) external onlyOwner { + require(index < currencyList.length, "Index out of bounds"); + require(currencyList[index] == token, "Index does not match token"); + + delete currencies[token]; + currencyList[index] = currencyList[currencyList.length - 1]; + currencyList.pop(); + } + + /** + * @notice Returns the list of all currency addresses. + * @return An array of addresses. + */ + function getCurrencies() public view returns (address[] memory) { + return currencyList; + } + + /** + * @notice Returns the configuration for a currency. + * @param token The address of the token. + * @return Currency configuration of the token. + */ + function getCurrencyConfig(address token) public view returns (CurrencyConfig memory) { + return currencies[token]; + } + + /** + * @notice Retrieves exchange rate between token and CELO. + * @param token The token address whose price is to be fetched. + * @return numerator The exchange rate numerator. + * @return denominator The exchange rate denominator. + */ + function getExchangeRate(address token) public view returns (uint256 numerator, uint256 denominator) { + require(currencies[token].oracle != address(0), "Currency not in the directory"); + (numerator, denominator) = IOracle(currencies[token].oracle).getExchangeRate(token); + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/FeeHandler.sol CELO/packages/contracts-bedrock/src/celo/FeeHandler.sol new file mode 100644 index 0000000000000000000000000000000000000000..00a1b0bde4fcb4af98c1cd85c71b2d45802d950c --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/FeeHandler.sol @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; +import "./common/Freezable.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; + +import "./common/interfaces/IFeeHandler.sol"; +import "./common/interfaces/IFeeHandlerSeller.sol"; + +// TODO move to IStableToken when it adds method getExchangeRegistryId +import "./interfaces/IStableTokenMento.sol"; +import "./common/interfaces/ICeloVersionedContract.sol"; +import "./common/interfaces/ICeloToken.sol"; +import "./stability/interfaces/ISortedOracles.sol"; + +// Using the minimal required signatures in the interfaces so more contracts could be compatible +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +// An implementation of FeeHandler as described in CIP-52 +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract FeeHandler is + Ownable, + Initializable, + UsingRegistry, + ICeloVersionedContract, + Freezable, + IFeeHandler, + ReentrancyGuard +{ + using FixidityLib for FixidityLib.Fraction; + using EnumerableSet for EnumerableSet.AddressSet; + + uint256 public constant FIXED1_UINT = 1000000000000000000000000; // TODO move to FIX and add check + + // Min units that can be burned + uint256 public constant MIN_BURN = 200; + + // last day the daily limits were updated + uint256 public lastLimitDay; + + FixidityLib.Fraction public burnFraction; // 80% + + address public feeBeneficiary; + + uint256 public celoToBeBurned; + + // This mapping can not be public because it contains a FixidityLib.Fraction + // and that'd be only supported with experimental features in this + // compiler version + mapping(address => TokenState) private tokenStates; + + struct TokenState { + address handler; + FixidityLib.Fraction maxSlippage; + // Max amounts that can be burned in a day for a token + uint256 dailySellLimit; + // Max amounts that can be burned today for a token + uint256 currentDaySellLimit; + uint256 toDistribute; + // Historical amounts burned by this contract + uint256 pastBurn; + } + + EnumerableSet.AddressSet private activeTokens; + + event SoldAndBurnedToken(address token, uint256 value); + event DailyLimitSet(address tokenAddress, uint256 newLimit); + event DailyLimitHit(address token, uint256 burning); + event MaxSlippageSet(address token, uint256 maxSlippage); + event DailySellLimitUpdated(uint256 amount); + event FeeBeneficiarySet(address newBeneficiary); + event BurnFractionSet(uint256 fraction); + event TokenAdded(address tokenAddress, address handlerAddress); + event TokenRemoved(address tokenAddress); + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + */ + function initialize( + address _registryAddress, + address newFeeBeneficiary, + uint256 newBurnFraction, + address[] calldata tokens, + address[] calldata handlers, + uint256[] calldata newLimits, + uint256[] calldata newMaxSlippages + ) + external + initializer + { + require(tokens.length == handlers.length, "handlers length should match tokens length"); + require(tokens.length == newLimits.length, "limits length should match tokens length"); + require(tokens.length == newMaxSlippages.length, "maxSlippage length should match tokens length"); + + _transferOwnership(msg.sender); + setRegistry(_registryAddress); + _setFeeBeneficiary(newFeeBeneficiary); + _setBurnFraction(newBurnFraction); + + for (uint256 i = 0; i < tokens.length; i++) { + _addToken(tokens[i], handlers[i]); + _setDailySellLimit(tokens[i], newLimits[i]); + _setMaxSplippage(tokens[i], newMaxSlippages[i]); + } + } + + // Without this the contract cant receive Celo as native transfer + receive() external payable { } + + /** + * @dev Returns the handler address for the specified token. + * @param tokenAddress The address of the token for which to return the handler. + * @return The address of the handler contract for the specified token. + */ + function getTokenHandler(address tokenAddress) external view returns (address) { + return tokenStates[tokenAddress].handler; + } + + /** + * @dev Returns a boolean indicating whether the specified token is active or not. + * @param tokenAddress The address of the token for which to retrieve the active status. + * @return A boolean representing the active status of the specified token. + */ + function getTokenActive(address tokenAddress) external view returns (bool) { + return activeTokens.contains(tokenAddress); + } + + /** + * @dev Returns the maximum slippage percentage for the specified token. + * @param tokenAddress The address of the token for which to retrieve the maximum + * slippage percentage. + * @return The maximum slippage percentage as a uint256 value. + */ + function getTokenMaxSlippage(address tokenAddress) external view returns (uint256) { + return FixidityLib.unwrap(tokenStates[tokenAddress].maxSlippage); + } + + /** + * @dev Returns the daily burn limit for the specified token. + * @param tokenAddress The address of the token for which to retrieve the daily burn limit. + * @return The daily burn limit as a uint256 value. + */ + function getTokenDailySellLimit(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].dailySellLimit; + } + + /** + * @dev Returns the current daily sell limit for the specified token. + * @param tokenAddress The address of the token for which to retrieve the current daily limit. + * @return The current daily limit as a uint256 value. + */ + function getTokenCurrentDaySellLimit(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].currentDaySellLimit; + } + + /** + * @dev Returns the amount of tokens available to distribute for the specified token. + * @param tokenAddress The address of the token for which to retrieve the amount of + * tokens available to distribute. + * @return The amount of tokens available to distribute as a uint256 value. + */ + function getTokenToDistribute(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].toDistribute; + } + + function getActiveTokens() public view returns (address[] memory) { + return activeTokens.values(); + } + + /** + * @dev Sets the fee beneficiary address to the specified address. + * @param beneficiary The address to set as the fee beneficiary. + */ + function setFeeBeneficiary(address beneficiary) external onlyOwner { + return _setFeeBeneficiary(beneficiary); + } + + function _setFeeBeneficiary(address beneficiary) private { + feeBeneficiary = beneficiary; + emit FeeBeneficiarySet(beneficiary); + } + + /** + * @dev Sets the burn fraction to the specified value. + * @param fraction The value to set as the burn fraction. + */ + function setBurnFraction(uint256 fraction) external onlyOwner { + return _setBurnFraction(fraction); + } + + function _setBurnFraction(uint256 newFraction) private { + FixidityLib.Fraction memory fraction = FixidityLib.wrap(newFraction); + require(FixidityLib.lte(fraction, FixidityLib.fixed1()), "Burn fraction must be less than or equal to 1"); + burnFraction = fraction; + emit BurnFractionSet(newFraction); + } + + /** + * @dev Sets the burn fraction to the specified value. Token has to have a handler set. + * @param tokenAddress The address of the token to sell + */ + function sell(address tokenAddress) external { + return _sell(tokenAddress); + } + + /** + * @dev Adds a new token to the contract with the specified token and handler addresses. + * @param tokenAddress The address of the token to add. + * @param handlerAddress The address of the handler contract for the specified token. + */ + function addToken(address tokenAddress, address handlerAddress) external onlyOwner { + _addToken(tokenAddress, handlerAddress); + } + + function _addToken(address tokenAddress, address handlerAddress) private { + require(handlerAddress != address(0), "Can't set handler to zero"); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = handlerAddress; + + activeTokens.add(tokenAddress); + emit TokenAdded(tokenAddress, handlerAddress); + } + + /** + * @notice Allows the owner to activate a specified token. + * @param tokenAddress The address of the token to be activated. + */ + function activateToken(address tokenAddress) external onlyOwner { + _activateToken(tokenAddress); + } + + function _activateToken(address tokenAddress) private { + TokenState storage tokenState = tokenStates[tokenAddress]; + require( + tokenState.handler != address(0) || tokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + "Handler has to be set to activate token" + ); + activeTokens.add(tokenAddress); + } + + /** + * @dev Deactivates the specified token by marking it as inactive. + * @param tokenAddress The address of the token to deactivate. + */ + function deactivateToken(address tokenAddress) external onlyOwner { + _deactivateToken(tokenAddress); + } + + function _deactivateToken(address tokenAddress) private { + activeTokens.remove(tokenAddress); + } + + /** + * @notice Allows the owner to set a handler contract for a specified token. + * @param tokenAddress The address of the token to set the handler for. + * @param handlerAddress The address of the handler contract to be set. + */ + function setHandler(address tokenAddress, address handlerAddress) external onlyOwner { + _setHandler(tokenAddress, handlerAddress); + } + + function _setHandler(address tokenAddress, address handlerAddress) private { + require(handlerAddress != address(0), "Can't set handler to zero, use deactivateToken"); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = handlerAddress; + } + + function removeToken(address tokenAddress) external onlyOwner { + _removeToken(tokenAddress); + } + + function _removeToken(address tokenAddress) private { + _deactivateToken(tokenAddress); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = address(0); + emit TokenRemoved(tokenAddress); + } + + function _sell(address tokenAddress) private onlyWhenNotFrozen nonReentrant { + IERC20 token = IERC20(tokenAddress); + + TokenState storage tokenState = tokenStates[tokenAddress]; + require(tokenState.handler != address(0), "Handler has to be set to sell token"); + require(FixidityLib.unwrap(tokenState.maxSlippage) != 0, "Max slippage has to be set to sell token"); + FixidityLib.Fraction memory balanceToProcess = + FixidityLib.newFixed(token.balanceOf(address(this)) - tokenState.toDistribute); + + uint256 balanceToBurn = (burnFraction.multiply(balanceToProcess).fromFixed()); + + tokenState.toDistribute = tokenState.toDistribute + balanceToProcess.fromFixed() - balanceToBurn; + + // small numbers cause rounding errors and zero case should be skipped + if (balanceToBurn < MIN_BURN) { + return; + } + + if (dailySellLimitHit(tokenAddress, balanceToBurn)) { + // in case the limit is hit, burn the max possible + balanceToBurn = tokenState.currentDaySellLimit; + emit DailyLimitHit(tokenAddress, balanceToBurn); + } + + token.transfer(tokenState.handler, balanceToBurn); + IFeeHandlerSeller handler = IFeeHandlerSeller(tokenState.handler); + + uint256 celoReceived = handler.sell( + tokenAddress, + registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + balanceToBurn, + FixidityLib.unwrap(tokenState.maxSlippage) + ); + + celoToBeBurned = celoToBeBurned + celoReceived; + tokenState.pastBurn = tokenState.pastBurn + balanceToBurn; + updateLimits(tokenAddress, balanceToBurn); + + emit SoldAndBurnedToken(tokenAddress, balanceToBurn); + } + + /** + * @dev Distributes the available tokens for the specified token address to the fee beneficiary. + * @param tokenAddress The address of the token for which to distribute the available tokens. + */ + function distribute(address tokenAddress) external { + return _distribute(tokenAddress); + } + + function _distribute(address tokenAddress) private onlyWhenNotFrozen nonReentrant { + require(feeBeneficiary != address(0), "Can't distribute to the zero address"); + IERC20 token = IERC20(tokenAddress); + uint256 tokenBalance = token.balanceOf(address(this)); + + TokenState storage tokenState = tokenStates[tokenAddress]; + require( + tokenState.handler != address(0) || tokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + "Handler has to be set to sell token" + ); + + // safty check to avoid a revert due balance + uint256 balanceToDistribute = Math.min(tokenBalance, tokenState.toDistribute); + + if (balanceToDistribute == 0) { + // don't distribute with zero balance + return; + } + + token.transfer(feeBeneficiary, balanceToDistribute); + tokenState.toDistribute = tokenState.toDistribute - balanceToDistribute; + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + /** + * @notice Allows owner to set max slippage for a token. + * @param token Address of the token to set. + * @param newMax New sllipage to set, as Fixidity fraction. + */ + function setMaxSplippage(address token, uint256 newMax) external onlyOwner { + _setMaxSplippage(token, newMax); + } + + function _setMaxSplippage(address token, uint256 newMax) private { + TokenState storage tokenState = tokenStates[token]; + require(newMax != 0, "Cannot set max slippage to zero"); + tokenState.maxSlippage = FixidityLib.wrap(newMax); + require( + FixidityLib.lte(tokenState.maxSlippage, FixidityLib.fixed1()), "Splippage must be less than or equal to 1" + ); + emit MaxSlippageSet(token, newMax); + } + + /** + * @notice Allows owner to set the daily burn limit for a token. + * @param token Address of the token to set. + * @param newLimit The new limit to set, in the token units. + */ + function setDailySellLimit(address token, uint256 newLimit) external onlyOwner { + _setDailySellLimit(token, newLimit); + } + + function _setDailySellLimit(address token, uint256 newLimit) private { + TokenState storage tokenState = tokenStates[token]; + tokenState.dailySellLimit = newLimit; + emit DailyLimitSet(token, newLimit); + } + + /** + * @dev Burns CELO tokens according to burnFraction. + */ + function burnCelo() external { + return _burnCelo(); + } + + /** + * @dev Distributes the available tokens for all registered tokens to the feeBeneficiary. + */ + function distributeAll() external { + return _distributeAll(); + } + + function _distributeAll() private { + for (uint256 i = 0; i < EnumerableSet.length(activeTokens); i++) { + address token = activeTokens.at(i); + _distribute(token); + } + // distribute Celo + _distribute(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + /** + * @dev Distributes the available tokens for all registered tokens to the feeBeneficiary. + */ + function handleAll() external { + return _handleAll(); + } + + function _handleAll() private { + for (uint256 i = 0; i < EnumerableSet.length(activeTokens); i++) { + // calling _handle would trigger may burn Celo and distributions + // that can be just batched at the end + address token = activeTokens.at(i); + _sell(token); + } + _distributeAll(); // distributes Celo as well + _burnCelo(); + } + + /** + * @dev Distributes the the token for to the feeBeneficiary. + */ + function handle(address tokenAddress) external { + return _handle(tokenAddress); + } + + function _handle(address tokenAddress) private { + // Celo doesn't have to be exchanged for anything + if (tokenAddress != registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)) { + _sell(tokenAddress); + } + _burnCelo(); + _distribute(tokenAddress); + _distribute(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + /** + * @notice Burns all the Celo balance of this contract. + */ + function _burnCelo() private { + TokenState storage tokenState = tokenStates[registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)]; + ICeloToken celo = ICeloToken(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + + uint256 balanceOfCelo = address(this).balance; + + uint256 balanceToProcess = balanceOfCelo - tokenState.toDistribute - celoToBeBurned; + uint256 currentBalanceToBurn = FixidityLib.newFixed(balanceToProcess).multiply(burnFraction).fromFixed(); + uint256 totalBalanceToBurn = currentBalanceToBurn + celoToBeBurned; + celo.burn(totalBalanceToBurn); + + celoToBeBurned = 0; + tokenState.toDistribute = tokenState.toDistribute + balanceToProcess - currentBalanceToBurn; + } + + /** + * @param token The address of the token to query. + * @return The amount burned for a token. + */ + function getPastBurnForToken(address token) external view returns (uint256) { + return tokenStates[token].pastBurn; + } + + /** + * @param token The address of the token to query. + * @param amountToBurn The amount of the token to burn. + * @return Returns true if burning amountToBurn would exceed the daily limit. + */ + function dailySellLimitHit(address token, uint256 amountToBurn) public returns (bool) { + TokenState storage tokenState = tokenStates[token]; + + if (tokenState.dailySellLimit == 0) { + // if no limit set, assume uncapped + return false; + } + + uint256 currentDay = block.timestamp / 1 days; + // Pattern borrowed from Reserve.sol + if (currentDay > lastLimitDay) { + lastLimitDay = currentDay; + tokenState.currentDaySellLimit = tokenState.dailySellLimit; + } + + return amountToBurn >= tokenState.currentDaySellLimit; + } + + /** + * @notice Updates the current day limit for a token. + * @param token The address of the token to query. + * @param amountBurned the amount of the token that was burned. + */ + function updateLimits(address token, uint256 amountBurned) private { + TokenState storage tokenState = tokenStates[token]; + + if (tokenState.dailySellLimit == 0) { + // if no limit set, assume uncapped + return; + } + tokenState.currentDaySellLimit = tokenState.currentDaySellLimit - amountBurned; + emit DailySellLimitUpdated(amountBurned); + } + + /** + * @notice Allows owner to transfer tokens of this contract. It's meant for governance to + * trigger use cases not contemplated in this contract. + * @param token The address of the token to transfer. + * @param recipient The address of the recipient to transfer the tokens to. + * @param value The amount of tokens to transfer. + * @return A boolean indicating whether the transfer was successful or not. + */ + function transfer(address token, address recipient, uint256 value) external onlyOwner returns (bool) { + return IERC20(token).transfer(recipient, value); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol CELO/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol new file mode 100644 index 0000000000000000000000000000000000000000..4d22125af4d647021d77e0ed4b59d09049dd6bac --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +import "./common/FixidityLib.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "./UsingRegistry.sol"; +import "./common/Initializable.sol"; + +// Abstract class for a FeeHandlerSeller, as defined in CIP-52 +// https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +abstract contract FeeHandlerSeller is Ownable, Initializable, UsingRegistry { + using FixidityLib for FixidityLib.Fraction; + + // Address of the token + // Minimal number of reports in SortedOracles contract + mapping(address => uint256) public minimumReports; + + event MinimumReportsSet(address tokenAddress, uint256 minimumReports); + event TokenSold(address soldTokenAddress, address boughtTokenAddress, uint256 amount); + + constructor(bool testingDeployment) Initializable(testingDeployment) { } + + function initialize( + address _registryAddress, + address[] calldata tokenAddresses, + uint256[] calldata newMininumReports + ) + external + initializer + { + _transferOwnership(msg.sender); + setRegistry(_registryAddress); + + for (uint256 i = 0; i < tokenAddresses.length; i++) { + _setMinimumReports(tokenAddresses[i], newMininumReports[i]); + } + } + + /** + * @notice Allows owner to set the minimum number of reports required. + * @param newMininumReports The new update minimum number of reports required. + */ + function setMinimumReports(address tokenAddress, uint256 newMininumReports) public onlyOwner { + _setMinimumReports(tokenAddress, newMininumReports); + } + + function _setMinimumReports(address tokenAddress, uint256 newMininumReports) internal { + minimumReports[tokenAddress] = newMininumReports; + emit MinimumReportsSet(tokenAddress, newMininumReports); + } + + /** + * @dev Calculates the minimum amount of tokens that should be received for the specified + * amount with the given mid-price and maximum slippage. + * @param midPriceNumerator The numerator of the mid-price for the token pair. + * @param midPriceDenominator The denominator of the mid-price for the token pair. + * @param amount The amount of tokens to be exchanged. + * @param maxSlippage The maximum slippage percentage as a fraction of the mid-price. + * @return The minimum amount of tokens that should be received as a uint256 value. + */ + function calculateMinAmount( + uint256 midPriceNumerator, + uint256 midPriceDenominator, + uint256 amount, + uint256 maxSlippage // as fraction + ) + public + pure + returns (uint256) + { + FixidityLib.Fraction memory maxSlippageFraction = FixidityLib.wrap(maxSlippage); + + FixidityLib.Fraction memory price = FixidityLib.newFixedFraction(midPriceNumerator, midPriceDenominator); + FixidityLib.Fraction memory amountFraction = FixidityLib.newFixed(amount); + FixidityLib.Fraction memory totalAmount = price.multiply(amountFraction); + + return totalAmount.subtract(price.multiply(maxSlippageFraction).multiply(amountFraction)).fromFixed(); + } + + /** + * @notice Allows owner to transfer tokens of this contract. It's meant for governance to + * trigger use cases not contemplated in this contract. + * @param token The address of the token to transfer. + * @param amount The amount of tokens to transfer. + * @param to The address of the recipient to transfer the tokens to. + * @return A boolean indicating whether the transfer was successful or not. + */ + function transfer(address token, uint256 amount, address to) external onlyOwner returns (bool) { + return IERC20(token).transfer(to, amount); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/GoldToken.sol CELO/packages/contracts-bedrock/src/celo/GoldToken.sol new file mode 100644 index 0000000000000000000000000000000000000000..e7236678670a7bedf86f7769ef74888dc5f2488c --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/GoldToken.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; +import "./CalledByVm.sol"; +import "./Initializable.sol"; +import "./interfaces/ICeloToken.sol"; +import "./common/interfaces/ICeloVersionedContract.sol"; + +contract GoldToken is Initializable, CalledByVm, UsingRegistry, IERC20, ICeloToken, ICeloVersionedContract { + // Address of the TRANSFER precompiled contract. + // solhint-disable state-visibility + address constant TRANSFER = address(0xff - 2); + string constant NAME = "Celo native asset"; + string constant SYMBOL = "CELO"; + uint8 constant DECIMALS = 18; + uint256 internal totalSupply_; + // solhint-enable state-visibility + + mapping(address => mapping(address => uint256)) internal allowed; + + // Burn address is 0xdEaD because truffle is having buggy behaviour with the zero address + address constant BURN_ADDRESS = address(0x000000000000000000000000000000000000dEaD); + + event TransferComment(string comment); + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 2, 0); + } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + * @param registryAddress Address of the Registry contract. + */ + function initialize(address registryAddress) external initializer { + totalSupply_ = 0; + _transferOwnership(msg.sender); + setRegistry(registryAddress); + } + + /** + * @notice Transfers CELO from one address to another. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + // solhint-disable-next-line no-simple-event-func-name + function transfer(address to, uint256 value) external returns (bool) { + return _transferWithCheck(to, value); + } + + /** + * @notice Transfers CELO from one address to another with a comment. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @param comment The transfer comment + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool) { + bool succeeded = _transferWithCheck(to, value); + emit TransferComment(comment); + return succeeded; + } + + /** + * @notice This function allows a user to burn a specific amount of tokens. + * Burning is implemented by sending tokens to the burn address. + * @param value: The amount of CELO to burn. + * @return True if burn was successful. + */ + function burn(uint256 value) external returns (bool) { + // not using transferWithCheck as the burn address can potentially be the zero address + return _transfer(BURN_ADDRESS, value); + } + + /** + * @notice Approve a user to transfer CELO on behalf of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function approve(address spender, uint256 value) external returns (bool) { + require(spender != address(0), "cannot set allowance for 0"); + allowed[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + /** + * @notice Increases the allowance of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The increment of the amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function increaseAllowance(address spender, uint256 value) external returns (bool) { + require(spender != address(0), "cannot set allowance for 0"); + uint256 oldValue = allowed[msg.sender][spender]; + uint256 newValue = oldValue + value; + allowed[msg.sender][spender] = newValue; + emit Approval(msg.sender, spender, newValue); + return true; + } + + /** + * @notice Decreases the allowance of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The decrement of the amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function decreaseAllowance(address spender, uint256 value) external returns (bool) { + uint256 oldValue = allowed[msg.sender][spender]; + uint256 newValue = oldValue - value; + allowed[msg.sender][spender] = newValue; + emit Approval(msg.sender, spender, newValue); + return true; + } + + /** + * @notice Transfers CELO from one address to another on behalf of a user. + * @param from The address to transfer CELO from. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool) { + require(to != address(0), "transfer attempted to reserved address 0x0"); + require(value <= balanceOf(from), "transfer value exceeded balance of sender"); + require(value <= allowed[from][msg.sender], "transfer value exceeded sender's allowance for spender"); + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(from, to, value)); + require(success, "CELO transfer failed"); + + allowed[from][msg.sender] = allowed[from][msg.sender] - value; + emit Transfer(from, to, value); + return true; + } + + /** + * @notice Mints new CELO and gives it to 'to'. + * @param to The account for which to mint tokens. + * @param value The amount of CELO to mint. + */ + function mint(address to, uint256 value) external onlyVm returns (bool) { + if (value == 0) { + return true; + } + + require(to != address(0), "mint attempted to reserved address 0x0"); + totalSupply_ = totalSupply_ + value; + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(address(0), to, value)); + require(success, "CELO transfer failed"); + + emit Transfer(address(0), to, value); + return true; + } + + /** + * @return The name of the CELO token. + */ + function name() external pure returns (string memory) { + return NAME; + } + + /** + * @return The symbol of the CELO token. + */ + function symbol() external pure returns (string memory) { + return SYMBOL; + } + + /** + * @return The number of decimal places to which CELO is divisible. + */ + function decimals() external pure returns (uint8) { + return DECIMALS; + } + + /** + * @return The total amount of CELO in existence, including what the burn address holds. + */ + function totalSupply() external view returns (uint256) { + return totalSupply_; + } + + /** + * @return The total amount of CELO in existence, not including what the burn address holds. + */ + function circulatingSupply() external view returns (uint256) { + return totalSupply_ - getBurnedAmount() - balanceOf(address(0)); + } + + /** + * @notice Gets the amount of owner's CELO allowed to be spent by spender. + * @param owner The owner of the CELO. + * @param spender The spender of the CELO. + * @return The amount of CELO owner is allowing spender to spend. + */ + function allowance(address owner, address spender) external view returns (uint256) { + return allowed[owner][spender]; + } + + /** + * @notice Increases the variable for total amount of CELO in existence. + * @param amount The amount to increase counter by + */ + function increaseSupply(uint256 amount) external onlyVm { + totalSupply_ = totalSupply_ + amount; + } + + /** + * @notice Gets the amount of CELO that has been burned. + * @return The total amount of Celo that has been sent to the burn address. + */ + function getBurnedAmount() public view returns (uint256) { + return balanceOf(BURN_ADDRESS); + } + + /** + * @notice Gets the balance of the specified address. + * @param owner The address to query the balance of. + * @return The balance of the specified address. + */ + function balanceOf(address owner) public view returns (uint256) { + return owner.balance; + } + + /** + * @notice internal CELO transfer from one address to another. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function _transfer(address to, uint256 value) internal returns (bool) { + require(value <= balanceOf(msg.sender), "transfer value exceeded balance of sender"); + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(msg.sender, to, value)); + require(success, "CELO transfer failed"); + emit Transfer(msg.sender, to, value); + return true; + } + + /** + * @notice Internal CELO transfer from one address to another. + * @param to The address to transfer CELO to. Zero address will revert. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function _transferWithCheck(address to, uint256 value) internal returns (bool) { + require(to != address(0), "transfer attempted to reserved address 0x0"); + return _transfer(to, value); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/Initializable.sol CELO/packages/contracts-bedrock/src/celo/Initializable.sol new file mode 100644 index 0000000000000000000000000000000000000000..7929728eef4ed9063c81aea6f2a0a1758d4ef728 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/Initializable.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +contract Initializable { + bool public initialized; + + modifier initializer() { + require(!initialized, "contract already initialized"); + initialized = true; + _; + } + + constructor(bool testingDeployment) { + if (!testingDeployment) { + initialized = true; + } + } +}
diff --git OP/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol CELO/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol new file mode 100644 index 0000000000000000000000000000000000000000..e5a9ff455f391f797bbc2ace5101c0ef58c3c192 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./interfaces/IStableTokenMento.sol"; + +import "./common/interfaces/IFeeHandlerSeller.sol"; +import "./stability/interfaces/ISortedOracles.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; + +import "./FeeHandlerSeller.sol"; + +// An implementation of FeeHandlerSeller supporting interfaces compatible with +// Mento +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract MentoFeeHandlerSeller is FeeHandlerSeller { + using FixidityLib for FixidityLib.Fraction; + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) FeeHandlerSeller(test) { } + + // without this line the contract can't receive native Celo transfers + receive() external payable { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 maxSlippage // as fraction, + ) + external + returns (uint256) + { + require( + buyTokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), "Buy token can only be gold token" + ); + + IStableTokenMento stableToken = IStableTokenMento(sellTokenAddress); + require(amount <= stableToken.balanceOf(address(this)), "Balance of token to burn not enough"); + + address exchangeAddress = registry.getAddressForOrDie(stableToken.getExchangeRegistryId()); + + IExchange exchange = IExchange(exchangeAddress); + + uint256 minAmount = 0; + + ISortedOracles sortedOracles = getSortedOracles(); + + require( + sortedOracles.numRates(sellTokenAddress) >= minimumReports[sellTokenAddress], + "Number of reports for token not enough" + ); + + (uint256 rateNumerator, uint256 rateDenominator) = sortedOracles.medianRate(sellTokenAddress); + minAmount = calculateMinAmount(rateNumerator, rateDenominator, amount, maxSlippage); + + // TODO an upgrade would be to compare using routers as well + stableToken.approve(exchangeAddress, amount); + exchange.sell(amount, minAmount, false); + + IERC20 goldToken = getGoldToken(); + uint256 celoAmount = goldToken.balanceOf(address(this)); + goldToken.transfer(msg.sender, celoAmount); + + emit TokenSold(sellTokenAddress, buyTokenAddress, amount); + return celoAmount; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/StableTokenV2.sol CELO/packages/contracts-bedrock/src/celo/StableTokenV2.sol new file mode 100644 index 0000000000000000000000000000000000000000..68632df65abc9d352de50b7f273afc491ff8a1b2 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/StableTokenV2.sol @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { ERC20PermitUpgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { IStableTokenV2 } from "./interfaces/IStableToken.sol"; +import { CalledByVm } from "./CalledByVm.sol"; + +/** + * @title ERC20 token with minting and burning permissioned to a broker and validators. + */ +contract StableTokenV2 is IStableTokenV2, ERC20PermitUpgradeable, CalledByVm, OwnableUpgradeable { + address public validators; + address public broker; + address public exchange; + + event TransferComment(string comment); + event BrokerUpdated(address broker); + event ValidatorsUpdated(address validators); + event ExchangeUpdated(address exchange); + + /** + * @dev Restricts a function so it can only be executed by an address that's allowed to mint. + * Currently that's the broker, validators, or exchange. + */ + modifier onlyMinter() { + address sender = _msgSender(); + require(sender == broker || sender == validators || sender == exchange, "StableTokenV2: not allowed to mint"); + _; + } + + /** + * @dev Restricts a function so it can only be executed by an address that's allowed to burn. + * Currently that's the broker or exchange. + */ + modifier onlyBurner() { + address sender = _msgSender(); + require(sender == broker || sender == exchange, "StableTokenV2: not allowed to burn"); + _; + } + + /** + * @notice The constructor for the StableTokenV2 contract. + * @dev Should be called with disable=true in deployments when + * it's accessed through a Proxy. + * Call this with disable=false during testing, when used + * without a proxy. + * @param disable Set to true to run `_disableInitializers()` inherited from + * openzeppelin-contracts-upgradeable/Initializable.sol + */ + constructor(bool disable) { + if (disable) { + _disableInitializers(); + } + } + + /** + * @notice Initializes a StableTokenV2. + * It keeps the same signature as the original initialize() function + * in legacy/StableToken.sol + * @param _name The name of the stable token (English) + * @param _symbol A short symbol identifying the token (e.g. "cUSD") + * @param initialBalanceAddresses Array of addresses with an initial balance. + * @param initialBalanceValues Array of balance values corresponding to initialBalanceAddresses. + * deprecated-param exchangeIdentifier String identifier of exchange in registry (for specific fiat pairs) + */ + function initialize( + // slither-disable-start shadowing-local + string calldata _name, + string calldata _symbol, + // slither-disable-end shadowing-local + address[] calldata initialBalanceAddresses, + uint256[] calldata initialBalanceValues + ) + external + initializer + { + __ERC20_init_unchained(_name, _symbol); + __ERC20Permit_init(_symbol); + _transferOwnership(_msgSender()); + + require(initialBalanceAddresses.length == initialBalanceValues.length, "Array length mismatch"); + for (uint256 i = 0; i < initialBalanceAddresses.length; i += 1) { + _mint(initialBalanceAddresses[i], initialBalanceValues[i]); + } + } + + /** + * @notice Initializes a StableTokenV2 contract + * when upgrading from legacy/StableToken.sol. + * It sets the addresses that were previously read from the Registry. + * It runs the ERC20PermitUpgradeable initializer. + * @dev This function is only callable once. + * @param _broker The address of the Broker contract. + * @param _validators The address of the Validators contract. + * @param _exchange The address of the Exchange contract. + */ + function initializeV2( + address _broker, + address _validators, + address _exchange + ) + external + reinitializer(2) + onlyOwner + { + _setBroker(_broker); + _setValidators(_validators); + _setExchange(_exchange); + __ERC20Permit_init(symbol()); + } + + /** + * @notice Sets the address of the Broker contract. + * @dev This function is only callable by the owner. + * @param _broker The address of the Broker contract. + */ + function setBroker(address _broker) external onlyOwner { + _setBroker(_broker); + } + + /** + * @notice Sets the address of the Validators contract. + * @dev This function is only callable by the owner. + * @param _validators The address of the Validators contract. + */ + function setValidators(address _validators) external onlyOwner { + _setValidators(_validators); + } + + /** + * @notice Sets the address of the Exchange contract. + * @dev This function is only callable by the owner. + * @param _exchange The address of the Exchange contract. + */ + function setExchange(address _exchange) external onlyOwner { + _setExchange(_exchange); + } + + /** + * @notice Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + * @param comment The transfer comment. + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool) { + emit TransferComment(comment); + return transfer(to, value); + } + + /** + * @notice Mints new StableToken and gives it to 'to'. + * @param to The account for which to mint tokens. + * @param value The amount of StableToken to mint. + */ + function mint(address to, uint256 value) external onlyMinter returns (bool) { + _mint(to, value); + return true; + } + + /** + * @notice Burns StableToken from the balance of msg.sender. + * @param value The amount of StableToken to burn. + */ + function burn(uint256 value) external onlyBurner returns (bool) { + _burn(msg.sender, value); + return true; + } + + /** + * @notice Set the address of the Broker contract and emit an event + * @param _broker The address of the Broker contract. + */ + function _setBroker(address _broker) internal { + broker = _broker; + emit BrokerUpdated(_broker); + } + + /** + * @notice Set the address of the Validators contract and emit an event + * @param _validators The address of the Validators contract. + */ + function _setValidators(address _validators) internal { + validators = _validators; + emit ValidatorsUpdated(_validators); + } + + /** + * @notice Set the address of the Exchange contract and emit an event + * @param _exchange The address of the Exchange contract. + */ + function _setExchange(address _exchange) internal { + exchange = _exchange; + emit ExchangeUpdated(_exchange); + } + + /// @inheritdoc ERC20Upgradeable + function transferFrom( + address from, + address to, + uint256 amount + ) + public + override(ERC20Upgradeable, IStableTokenV2) + returns (bool) + { + return ERC20Upgradeable.transferFrom(from, to, amount); + } + + /// @inheritdoc ERC20Upgradeable + function transfer(address to, uint256 amount) public override(ERC20Upgradeable, IStableTokenV2) returns (bool) { + return ERC20Upgradeable.transfer(to, amount); + } + + /// @inheritdoc ERC20Upgradeable + function balanceOf(address account) public view override(ERC20Upgradeable, IStableTokenV2) returns (uint256) { + return ERC20Upgradeable.balanceOf(account); + } + + /// @inheritdoc ERC20Upgradeable + function approve( + address spender, + uint256 amount + ) + public + override(ERC20Upgradeable, IStableTokenV2) + returns (bool) + { + return ERC20Upgradeable.approve(spender, amount); + } + + /// @inheritdoc ERC20Upgradeable + function allowance( + address owner, + address spender + ) + public + view + override(ERC20Upgradeable, IStableTokenV2) + returns (uint256) + { + return ERC20Upgradeable.allowance(owner, spender); + } + + /// @inheritdoc ERC20Upgradeable + function totalSupply() public view override(ERC20Upgradeable, IStableTokenV2) returns (uint256) { + return ERC20Upgradeable.totalSupply(); + } + + /// @inheritdoc ERC20PermitUpgradeable + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) + public + override(ERC20PermitUpgradeable, IStableTokenV2) + { + ERC20PermitUpgradeable.permit(owner, spender, value, deadline, v, r, s); + } + + /** + * @notice Reserve balance for making payments for gas in this StableToken currency. + * @param from The account to reserve balance from + * @param value The amount of balance to reserve + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. After the tx is executed, gas is refunded to the sender and credited to the + * various tx fee recipients via a call to `creditGasFees`. + */ + function debitGasFees(address from, uint256 value) external onlyVm { + _burn(from, value); + } + + /** + * @notice Alternative function to credit balance after making payments + * for gas in this StableToken currency. + * @param from The account to debit balance from + * @param feeRecipient Coinbase address + * @param gatewayFeeRecipient Gateway address + * @param communityFund Community fund address + * @param refund amount to be refunded by the VM + * @param tipTxFee Coinbase fee + * @param baseTxFee Community fund fee + * @param gatewayFee Gateway fee + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. Before the tx is executed, gas is debited from the sender via a call to + * `debitGasFees`. + */ + function creditGasFees( + address from, + address feeRecipient, + address gatewayFeeRecipient, + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256 gatewayFee, + uint256 baseTxFee + ) + external + onlyVm + { + // slither-disable-next-line uninitialized-local + uint256 amountToBurn; + _mint(from, refund + tipTxFee + gatewayFee + baseTxFee); + + if (feeRecipient != address(0)) { + _transfer(from, feeRecipient, tipTxFee); + } else if (tipTxFee > 0) { + amountToBurn += tipTxFee; + } + + if (gatewayFeeRecipient != address(0)) { + _transfer(from, gatewayFeeRecipient, gatewayFee); + } else if (gatewayFee > 0) { + amountToBurn += gatewayFee; + } + + if (communityFund != address(0)) { + _transfer(from, communityFund, baseTxFee); + } else if (baseTxFee > 0) { + amountToBurn += baseTxFee; + } + + if (amountToBurn > 0) { + _burn(from, amountToBurn); + } + } +}
diff --git OP/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol CELO/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol new file mode 100644 index 0000000000000000000000000000000000000000..54ce14eaf37cfd30695729e4a2990b294d589b86 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; + +import "./common/interfaces/IFeeHandlerSeller.sol"; +import "./stability/interfaces/ISortedOracles.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; +import "./FeeHandlerSeller.sol"; + +import "./uniswap/interfaces/IUniswapV2RouterMin.sol"; +import "./uniswap/interfaces/IUniswapV2FactoryMin.sol"; + +// An implementation of FeeHandlerSeller supporting interfaces compatible with +// Uniswap V2 API +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract UniswapFeeHandlerSeller is FeeHandlerSeller { + using FixidityLib for FixidityLib.Fraction; + using EnumerableSet for EnumerableSet.AddressSet; + + uint256 constant MAX_TIMESTAMP_BLOCK_EXCHANGE = 20; + uint256 constant MAX_NUMBER_ROUTERS_PER_TOKEN = 3; + mapping(address => EnumerableSet.AddressSet) private routerAddresses; + + event ReceivedQuote(address indexed tokneAddress, address indexed router, uint256 quote); + event RouterUsed(address router); + event RouterAddressSet(address token, address router); + event RouterAddressRemoved(address token, address router); + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) FeeHandlerSeller(test) { } + + // without this line the contract can't receive native Celo transfers + receive() external payable { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + /** + * @notice Allows owner to set the router for a token. + * @param token Address of the token to set. + * @param router The new router. + */ + function setRouter(address token, address router) external onlyOwner { + _setRouter(token, router); + } + + function _setRouter(address token, address router) private { + require(router != address(0), "Router can't be address zero"); + routerAddresses[token].add(router); + require(routerAddresses[token].values().length <= MAX_NUMBER_ROUTERS_PER_TOKEN, "Max number of routers reached"); + emit RouterAddressSet(token, router); + } + + /** + * @notice Allows owner to remove a router for a token. + * @param token Address of the token. + * @param router Address of the router to remove. + */ + function removeRouter(address token, address router) external onlyOwner { + routerAddresses[token].remove(router); + emit RouterAddressRemoved(token, router); + } + + /** + * @notice Get the list of routers for a token. + * @param token The address of the token to query. + * @return An array of all the allowed router. + */ + function getRoutersForToken(address token) external view returns (address[] memory) { + return routerAddresses[token].values(); + } + + /** + * @dev Calculates the minimum amount of tokens that can be received for a given amount of sell tokens, + * taking into account the slippage and the rates of the sell token and CELO token on the Uniswap V2 pair. + * @param sellTokenAddress The address of the sell token. + * @param maxSlippage The maximum slippage allowed. + * @param amount The amount of sell tokens to be traded. + * @param bestRouter The Uniswap V2 router with the best price. + * @return The minimum amount of tokens that can be received. + */ + function calculateAllMinAmount( + address sellTokenAddress, + uint256 maxSlippage, + uint256 amount, + IUniswapV2RouterMin bestRouter + ) + private + view + returns (uint256) + { + ISortedOracles sortedOracles = getSortedOracles(); + uint256 minReports = minimumReports[sellTokenAddress]; + + require(sortedOracles.numRates(sellTokenAddress) >= minReports, "Number of reports for token not enough"); + + uint256 minimalSortedOracles = 0; + // if minimumReports for this token is zero, assume the check is not needed + if (minReports > 0) { + (uint256 rateNumerator, uint256 rateDenominator) = sortedOracles.medianRate(sellTokenAddress); + + minimalSortedOracles = calculateMinAmount(rateNumerator, rateDenominator, amount, maxSlippage); + } + + IERC20 celoToken = getGoldToken(); + address pair = IUniswapV2FactoryMin(bestRouter.factory()).getPair(sellTokenAddress, address(celoToken)); + uint256 minAmountPair = + calculateMinAmount(IERC20(sellTokenAddress).balanceOf(pair), celoToken.balanceOf(pair), amount, maxSlippage); + + return Math.max(minAmountPair, minimalSortedOracles); + } + + // This function explicitly defines few variables because it was getting error "stack too deep" + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 maxSlippage // as fraction, + ) + external + returns (uint256) + { + require( + buyTokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), "Buy token can only be gold token" + ); + + require(routerAddresses[sellTokenAddress].values().length > 0, "routerAddresses should be non empty"); + + // An improvement to this function would be to allow the user to pass a path as argument + // and if it generates a better outcome that the ones enabled that gets used + // and the user gets a reward + + IERC20 celoToken = getGoldToken(); + + IUniswapV2RouterMin bestRouter; + uint256 bestRouterQuote = 0; + + address[] memory path = new address[](2); + + path[0] = sellTokenAddress; + path[1] = address(celoToken); + + for (uint256 i = 0; i < routerAddresses[sellTokenAddress].values().length; i++) { + address poolAddress = routerAddresses[sellTokenAddress].at(i); + IUniswapV2RouterMin router = IUniswapV2RouterMin(poolAddress); + + // Using the second return value becuase it's the last argument, + // the previous values show how many tokens are exchanged in each path + // so the first value would be equivalent to balanceToBurn + uint256 wouldGet = router.getAmountsOut(amount, path)[1]; + + emit ReceivedQuote(sellTokenAddress, poolAddress, wouldGet); + if (wouldGet > bestRouterQuote) { + bestRouterQuote = wouldGet; + bestRouter = router; + } + } + + require(bestRouterQuote != 0, "Can't exchange with zero quote"); + + uint256 minAmount = 0; + minAmount = calculateAllMinAmount(sellTokenAddress, maxSlippage, amount, bestRouter); + + IERC20(sellTokenAddress).approve(address(bestRouter), amount); + bestRouter.swapExactTokensForTokens( + amount, minAmount, path, address(this), block.timestamp + MAX_TIMESTAMP_BLOCK_EXCHANGE + ); + + uint256 celoAmount = celoToken.balanceOf(address(this)); + celoToken.transfer(msg.sender, celoAmount); + emit RouterUsed(address(bestRouter)); + emit TokenSold(sellTokenAddress, buyTokenAddress, amount); + return celoAmount; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/UsingRegistry.sol CELO/packages/contracts-bedrock/src/celo/UsingRegistry.sol new file mode 100644 index 0000000000000000000000000000000000000000..b5bf928d11f22346afcada6a6f830ffb8f4eee8c --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/UsingRegistry.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./interfaces/IAccounts.sol"; +import "./interfaces/IFreezer.sol"; +import "./interfaces/ICeloRegistry.sol"; + +import "./governance/interfaces/IElection.sol"; +import "./governance/interfaces/IGovernance.sol"; +import "./governance/interfaces/ILockedGold.sol"; +import "./governance/interfaces/IValidators.sol"; + +import "./identity/interfaces/IRandom.sol"; +import "./identity/interfaces/IAttestations.sol"; + +import "./stability/interfaces/ISortedOracles.sol"; + +import "./mento/interfaces/IExchange.sol"; +import "./mento/interfaces/IReserve.sol"; +import "./mento/interfaces/IStableToken.sol"; + +contract UsingRegistry is Ownable { + event RegistrySet(address indexed registryAddress); + + // solhint-disable state-visibility + bytes32 constant ACCOUNTS_REGISTRY_ID = keccak256(abi.encodePacked("Accounts")); + bytes32 constant ATTESTATIONS_REGISTRY_ID = keccak256(abi.encodePacked("Attestations")); + bytes32 constant DOWNTIME_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("DowntimeSlasher")); + bytes32 constant DOUBLE_SIGNING_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("DoubleSigningSlasher")); + bytes32 constant ELECTION_REGISTRY_ID = keccak256(abi.encodePacked("Election")); + bytes32 constant EXCHANGE_REGISTRY_ID = keccak256(abi.encodePacked("Exchange")); + bytes32 constant FREEZER_REGISTRY_ID = keccak256(abi.encodePacked("Freezer")); + bytes32 constant GOLD_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("GoldToken")); + bytes32 constant GOVERNANCE_REGISTRY_ID = keccak256(abi.encodePacked("Governance")); + bytes32 constant GOVERNANCE_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("GovernanceSlasher")); + bytes32 constant LOCKED_GOLD_REGISTRY_ID = keccak256(abi.encodePacked("LockedGold")); + bytes32 constant RESERVE_REGISTRY_ID = keccak256(abi.encodePacked("Reserve")); + bytes32 constant RANDOM_REGISTRY_ID = keccak256(abi.encodePacked("Random")); + bytes32 constant SORTED_ORACLES_REGISTRY_ID = keccak256(abi.encodePacked("SortedOracles")); + bytes32 constant STABLE_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("StableToken")); + bytes32 constant VALIDATORS_REGISTRY_ID = keccak256(abi.encodePacked("Validators")); + // solhint-enable state-visibility + + ICeloRegistry public registry; + + modifier onlyRegisteredContract(bytes32 identifierHash) { + require(registry.getAddressForOrDie(identifierHash) == msg.sender, "only registered contract"); + _; + } + + modifier onlyRegisteredContracts(bytes32[] memory identifierHashes) { + require(registry.isOneOf(identifierHashes, msg.sender), "only registered contracts"); + _; + } + + /** + * @notice Updates the address pointing to a Registry contract. + * @param registryAddress The address of a registry contract for routing to other contracts. + */ + function setRegistry(address registryAddress) public onlyOwner { + require(registryAddress != address(0), "Cannot register the null address"); + registry = ICeloRegistry(registryAddress); + emit RegistrySet(registryAddress); + } + + function getAccounts() internal view returns (IAccounts) { + return IAccounts(registry.getAddressForOrDie(ACCOUNTS_REGISTRY_ID)); + } + + function getAttestations() internal view returns (IAttestations) { + return IAttestations(registry.getAddressForOrDie(ATTESTATIONS_REGISTRY_ID)); + } + + function getElection() internal view returns (IElection) { + return IElection(registry.getAddressForOrDie(ELECTION_REGISTRY_ID)); + } + + function getExchange() internal view returns (IExchange) { + return IExchange(registry.getAddressForOrDie(EXCHANGE_REGISTRY_ID)); + } + + function getFreezer() internal view returns (IFreezer) { + return IFreezer(registry.getAddressForOrDie(FREEZER_REGISTRY_ID)); + } + + function getGoldToken() internal view returns (IERC20) { + return IERC20(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + function getGovernance() internal view returns (IGovernance) { + return IGovernance(registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID)); + } + + function getLockedGold() internal view returns (ILockedGold) { + return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID)); + } + + function getRandom() internal view returns (IRandom) { + return IRandom(registry.getAddressForOrDie(RANDOM_REGISTRY_ID)); + } + + function getReserve() internal view returns (IReserve) { + return IReserve(registry.getAddressForOrDie(RESERVE_REGISTRY_ID)); + } + + function getSortedOracles() internal view returns (ISortedOracles) { + return ISortedOracles(registry.getAddressForOrDie(SORTED_ORACLES_REGISTRY_ID)); + } + + function getStableToken() internal view returns (IStableToken) { + return IStableToken(registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID)); + } + + function getValidators() internal view returns (IValidators) { + return IValidators(registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID)); + } +}
diff --git OP/packages/contracts-bedrock/src/libraries/SafeCall.sol CELO/packages/contracts-bedrock/src/libraries/SafeCall.sol index 78603993b8ad0be09d426be9473d9d30494eb6c5..c2c4e635f0fbea9f04ff10565b4c25df712324f3 100644 --- OP/packages/contracts-bedrock/src/libraries/SafeCall.sol +++ CELO/packages/contracts-bedrock/src/libraries/SafeCall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0;   /// @title SafeCall /// @notice Perform low level safe calls @@ -57,6 +57,14 @@ 0, // outloc 0 // outlen ) } + } + + /// @notice Perform a low level call without copying any returndata + /// @param _target Address to call + /// @param _value Amount of value to pass to the call + /// @param _calldata Calldata to pass to the call + function call(address _target, uint256 _value, bytes memory _calldata) internal returns (bool success_) { + success_ = call({ _target: _target, _gas: gasleft(), _value: _value, _calldata: _calldata }); }   /// @notice Helper function to determine if there is sufficient gas remaining within the context
diff --git OP/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol CELO/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol index 43ddd65424f895b007d2eab8f5907b281c714f74..fdf16f8a7371964628efe2f456c2601ed4cb3657 100644 --- OP/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol +++ CELO/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol @@ -5,6 +5,7 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { ILegacyMintableERC20, IOptimismMintableERC20 } from "src/universal/IOptimismMintableERC20.sol"; import { ISemver } from "src/universal/ISemver.sol"; +import { AbstractFeeCurrency } from "src/celo/AbstractFeeCurrency.sol";   /// @title OptimismMintableERC20 /// @notice OptimismMintableERC20 is a standard extension of the base ERC20 token contract designed @@ -12,7 +13,7 @@ /// to allow the StandardBridge contracts to mint and burn tokens. This makes it possible to /// use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa. /// Designed to be backwards compatible with the older StandardL2ERC20 token which was only /// meant for use on L2. -contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, ISemver { +contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, ISemver, AbstractFeeCurrency { /// @notice Address of the corresponding version of this token on the remote chain. address public immutable REMOTE_TOKEN;
diff --git OP/packages/contracts-bedrock/test/L2Genesis.t.sol CELO/packages/contracts-bedrock/test/L2Genesis.t.sol index f851f62d634d87b5b688acc59c208b8a4c9f4c90..789257a909de25fb184a06bc9fa0de5febf1530f 100644 --- OP/packages/contracts-bedrock/test/L2Genesis.t.sol +++ CELO/packages/contracts-bedrock/test/L2Genesis.t.sol @@ -37,7 +37,7 @@ string[] memory commands = new string[](3); commands[0] = "bash"; commands[1] = "-c"; commands[2] = string.concat("rm ", path); - Process.run(commands); + Process.run({ _command: commands, _allowEmpty: true }); }   /// @notice Returns the number of top level keys in a JSON object at a given @@ -181,6 +181,7 @@ /// @notice Tests the number of accounts in the genesis setup function _test_allocs_size(string memory _path) internal { genesis.cfg().setFundDevAccounts(false); + genesis.cfg().setDeployCeloContracts(true); genesis.runWithLatestLocal(_dummyL1Deps()); genesis.writeGenesisAllocs(_path);   @@ -190,6 +191,7 @@ expected += 21; // predeploy implementations (excl. legacy erc20-style eth and legacy message sender) expected += 256; // precompiles expected += 12; // preinstalls expected += 1; // 4788 deployer account + expected += 14; // Celo contracts // 16 prefunded dev accounts are excluded assertEq(expected, getJSONKeyCount(_path), "key count check");
diff --git OP/packages/contracts-bedrock/test/Safe/DeployOwnership.t.sol CELO/packages/contracts-bedrock/test/Safe/DeployOwnership.t.sol index cc4f58508ab1397afc197c48fc0a3f8dc7ea1844..b3a856e68a406ed3ccdec503437374447c0e74ed 100644 --- OP/packages/contracts-bedrock/test/Safe/DeployOwnership.t.sol +++ CELO/packages/contracts-bedrock/test/Safe/DeployOwnership.t.sol @@ -8,7 +8,7 @@ SecurityCouncilConfig, GuardianConfig, DeputyGuardianModuleConfig, LivenessModuleConfig -} from "scripts/DeployOwnership.s.sol"; +} from "scripts/deploy/DeployOwnership.s.sol"; import { Test } from "forge-std/Test.sol";   import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
diff --git OP/packages/contracts-bedrock/test/Specs.t.sol CELO/packages/contracts-bedrock/test/Specs.t.sol index e3d0d7cc7d6f23dc0462b2cdbd10ff8fce9c6657..14a17559ea4f6eeeeb605923a298578d7a2a7852 100644 --- OP/packages/contracts-bedrock/test/Specs.t.sol +++ CELO/packages/contracts-bedrock/test/Specs.t.sol @@ -25,13 +25,16 @@ PROPOSER, CHALLENGER, SYSTEMCONFIGOWNER, GUARDIAN, + DEPUTYGUARDIAN, MESSENGER, L1PROXYADMINOWNER, GOVERNANCETOKENOWNER, MINTMANAGEROWNER, DATAAVAILABILITYCHALLENGEOWNER, DISPUTEGAMEFACTORYOWNER, - DELAYEDWETHOWNER + DELAYEDWETHOWNER, + COUNCILSAFE, + COUNCILSAFEOWNER }   /// @notice Represents the specification of a function. @@ -47,6 +50,7 @@ bool pausable; }   mapping(string => mapping(bytes4 => Spec)) specs; + mapping(Role => Spec[]) public specsByRole; mapping(string => uint256) public numEntries; uint256 numSpecs;   @@ -247,7 +251,7 @@ _pausable: true }); _addSpec({ _name: "OptimismPortal", _sel: _getSel("finalizedWithdrawals(bytes32)") }); _addSpec({ _name: "OptimismPortal", _sel: _getSel("guardian()") }); - _addSpec({ _name: "OptimismPortal", _sel: _getSel("initialize(address,address,address)") }); + _addSpec({ _name: "OptimismPortal", _sel: _getSel("initialize(address,address,address,uint256)") }); _addSpec({ _name: "OptimismPortal", _sel: _getSel("isOutputFinalized(uint256)") }); _addSpec({ _name: "OptimismPortal", _sel: _getSel("l2Oracle()") }); _addSpec({ _name: "OptimismPortal", _sel: _getSel("l2Sender()") }); @@ -279,7 +283,7 @@ _pausable: true }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("finalizedWithdrawals(bytes32)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("guardian()") }); - _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("initialize(address,address,address)") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("initialize(address,address,address,uint256)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("isOutputFinalized(uint256)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("l2Oracle()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("l2Sender()") }); @@ -725,9 +729,13 @@ }); _addSpec({ _name: "DisputeGameFactory", _sel: _getSel("setImplementation(uint32,address)"), - _auth: Role.GUARDIAN + _auth: Role.DISPUTEGAMEFACTORYOWNER }); - _addSpec({ _name: "DisputeGameFactory", _sel: _getSel("setInitBond(uint32,uint256)"), _auth: Role.GUARDIAN }); + _addSpec({ + _name: "DisputeGameFactory", + _sel: _getSel("setInitBond(uint32,uint256)"), + _auth: Role.DISPUTEGAMEFACTORYOWNER + }); _addSpec({ _name: "DisputeGameFactory", _sel: _getSel("transferOwnership(address)"), @@ -743,11 +751,11 @@ _addSpec({ _name: "DelayedWETH", _sel: _getSel("config()") }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("decimals()") }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("delay()") }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("deposit()") }); - _addSpec({ _name: "DelayedWETH", _sel: _getSel("hold(address,uint256)"), _auth: Role.GUARDIAN }); + _addSpec({ _name: "DelayedWETH", _sel: _getSel("hold(address,uint256)"), _auth: Role.DELAYEDWETHOWNER }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("initialize(address,address)") }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("name()") }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("owner()") }); - _addSpec({ _name: "DelayedWETH", _sel: _getSel("recover(uint256)"), _auth: Role.GUARDIAN }); + _addSpec({ _name: "DelayedWETH", _sel: _getSel("recover(uint256)"), _auth: Role.DELAYEDWETHOWNER }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("renounceOwnership()"), _auth: Role.DELAYEDWETHOWNER }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("symbol()") }); _addSpec({ _name: "DelayedWETH", _sel: _getSel("totalSupply()") }); @@ -772,6 +780,51 @@ _addSpec({ _name: "WETH98", _sel: _getSel("totalSupply()") }); _addSpec({ _name: "WETH98", _sel: _getSel("transfer(address,uint256)") }); _addSpec({ _name: "WETH98", _sel: _getSel("transferFrom(address,address,uint256)") }); _addSpec({ _name: "WETH98", _sel: _getSel("withdraw(uint256)") }); + + // DeputyGuardianModule + _addSpec({ + _name: "DeputyGuardianModule", + _sel: _getSel("blacklistDisputeGame(address,address)"), + _auth: Role.DEPUTYGUARDIAN + }); + _addSpec({ + _name: "DeputyGuardianModule", + _sel: _getSel("setRespectedGameType(address,uint32)"), + _auth: Role.DEPUTYGUARDIAN + }); + _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("pause()"), _auth: Role.DEPUTYGUARDIAN }); + _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("unpause()"), _auth: Role.DEPUTYGUARDIAN }); + _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("deputyGuardian()") }); + _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("safe()") }); + _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("superchainConfig()") }); + _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("version()") }); + + // LivenessGuard + _addSpec({ _name: "LivenessGuard", _sel: _getSel("checkAfterExecution(bytes32,bool)"), _auth: Role.COUNCILSAFE }); + _addSpec({ + _name: "LivenessGuard", + _sel: _getSel( + "checkTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes,address)" + ), + _auth: Role.COUNCILSAFE + }); + _addSpec({ _name: "LivenessGuard", _sel: _getSel("lastLive(address)") }); + _addSpec({ _name: "LivenessGuard", _sel: _getSel("safe()") }); + _addSpec({ _name: "LivenessGuard", _sel: _getSel("showLiveness()"), _auth: Role.COUNCILSAFEOWNER }); + _addSpec({ _name: "LivenessGuard", _sel: _getSel("version()") }); + + // LivenessModule + _addSpec({ _name: "LivenessModule", _sel: _getSel("canRemove(address)") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("fallbackOwner()") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("getRequiredThreshold(uint256)") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("livenessGuard()") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("livenessInterval()") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("minOwners()") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("ownershipTransferredToFallback()") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("removeOwners(address[],address[])") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("safe()") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("thresholdPercentage()") }); + _addSpec({ _name: "LivenessModule", _sel: _getSel("version()") }); }   /// @dev Computes the selector from a function signature. @@ -781,8 +834,10 @@ }   /// @dev Adds a spec for a function. function _addSpec(string memory _name, bytes4 _sel, Role _auth, bool _pausable) internal { - specs[_name][_sel] = Spec({ name: _name, sel: _sel, auth: _auth, pausable: _pausable }); + Spec memory spec = Spec({ name: _name, sel: _sel, auth: _auth, pausable: _pausable }); + specs[_name][_sel] = spec; numEntries[_name]++; + specsByRole[_auth].push(spec); numSpecs++; }   @@ -803,11 +858,13 @@ }   /// @notice Ensures that there's an auth spec for every L1 contract function. function testContractAuth() public { - string[] memory pathExcludes = new string[](2); + string[] memory pathExcludes = new string[](3); pathExcludes[0] = "src/dispute/interfaces/*"; pathExcludes[1] = "src/dispute/lib/*"; - Abi[] memory abis = - ForgeArtifacts.getContractFunctionAbis("src/{L1,dispute,governance,universal/ProxyAdmin.sol}", pathExcludes); + pathExcludes[2] = "src/Safe/SafeSigners.sol"; + Abi[] memory abis = ForgeArtifacts.getContractFunctionAbis( + "src/{L1,dispute,governance,Safe,universal/ProxyAdmin.sol}", pathExcludes + );   uint256 numCheckedEntries = 0; for (uint256 i = 0; i < abis.length; i++) { @@ -839,5 +896,34 @@ numCheckedEntries++; } } assertEq(numSpecs, numCheckedEntries, "Some specs were not checked"); + } + + /// @dev Asserts that two roles are equal by comparing their uint256 representations. + function _assertRolesEq(Role leftRole, Role rightRole) internal pure { + assertEq(uint256(leftRole), uint256(rightRole)); + } + + /// @notice Ensures that the DeputyGuardian is authorized to take all Guardian actions. + function testDeputyGuardianAuth() public view { + assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, specsByRole[Role.GUARDIAN].length); + assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, 4); + + mapping(bytes4 => Spec) storage dgmFuncSpecs = specs["DeputyGuardianModule"]; + mapping(bytes4 => Spec) storage superchainConfigFuncSpecs = specs["SuperchainConfig"]; + mapping(bytes4 => Spec) storage portal2FuncSpecs = specs["OptimismPortal2"]; + + // Ensure that for each of the DeputyGuardianModule's methods there is a corresponding method on another + // system contract authed to the Guardian role. + _assertRolesEq(dgmFuncSpecs[_getSel("pause()")].auth, Role.DEPUTYGUARDIAN); + _assertRolesEq(superchainConfigFuncSpecs[_getSel("pause(string)")].auth, Role.GUARDIAN); + + _assertRolesEq(dgmFuncSpecs[_getSel("unpause()")].auth, Role.DEPUTYGUARDIAN); + _assertRolesEq(superchainConfigFuncSpecs[_getSel("unpause()")].auth, Role.GUARDIAN); + + _assertRolesEq(dgmFuncSpecs[_getSel("blacklistDisputeGame(address,address)")].auth, Role.DEPUTYGUARDIAN); + _assertRolesEq(portal2FuncSpecs[_getSel("blacklistDisputeGame(address)")].auth, Role.GUARDIAN); + + _assertRolesEq(dgmFuncSpecs[_getSel("setRespectedGameType(address,uint32)")].auth, Role.DEPUTYGUARDIAN); + _assertRolesEq(portal2FuncSpecs[_getSel("setRespectedGameType(uint32)")].auth, Role.GUARDIAN); } }
diff --git OP/packages/contracts-bedrock/test/cannon/MIPS.t.sol CELO/packages/contracts-bedrock/test/cannon/MIPS.t.sol index f5c9c3066c7b6714c22c0b49fb99b2a1efc62d64..3f843f3e0d028e3f6045942ce5a6c518c99ab7e1 100644 --- OP/packages/contracts-bedrock/test/cannon/MIPS.t.sol +++ CELO/packages/contracts-bedrock/test/cannon/MIPS.t.sol @@ -1470,7 +1470,7 @@ function test_fcntl_succeeds() external { uint32 insn = 0x0000000c; // syscall (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); - state.registers[2] = 4055; // fnctl syscall + state.registers[2] = 4055; // fcntl syscall state.registers[4] = 0x0; // a0 state.registers[5] = 0x3; // a1
diff --git OP/packages/contracts-bedrock/test/invariants/InvariantTest.sol CELO/packages/contracts-bedrock/test/invariants/InvariantTest.sol index a188bcdf27a05fc11141134a95bd91c847b0c5f9..eea6c158b3577d456a20654eeebd9bc1a9ef40ad 100644 --- OP/packages/contracts-bedrock/test/invariants/InvariantTest.sol +++ CELO/packages/contracts-bedrock/test/invariants/InvariantTest.sol @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15;   import { FFIInterface } from "test/setup/FFIInterface.sol"; -import { Deploy } from "scripts/Deploy.s.sol"; +import { Deploy } from "scripts/deploy/Deploy.s.sol"; import { Test } from "forge-std/Test.sol";   /// @title InvariantTest
diff --git OP/packages/contracts-bedrock/test/kontrol/README.md CELO/packages/contracts-bedrock/test/kontrol/README.md index ccf8781fce0be9a56bf453198756c0e08d787da1..e539c3c109d535b00d0c31f7cc2f306857a51489 100644 --- OP/packages/contracts-bedrock/test/kontrol/README.md +++ CELO/packages/contracts-bedrock/test/kontrol/README.md @@ -111,7 +111,7 @@ These are the instructions to add a new proof to this project. If all functions involved in the new proof are from a contract already deployed by [`KontrolDeployment`](./deployment/KontrolDeployment.sol) the first two steps can be skipped.   #### Make Kontrol aware of the new contract being tested   -The `runKontrolDeployment` function of [`KontrolDeployment`](./deployment/KontrolDeployment.sol) partially reproduces the deployment process laid out in the `_run` function of [`Deploy.s.sol`](../../scripts/Deploy.s.sol). `runKontrolDeployment` has the `stateDiff` modifier to make use of [Foundry's state diff cheatcodes](https://book.getfoundry.sh/cheatcodes/start-state-diff-recording). Kontrol utilizes the JSON resulting from this modifier for two purposes: +The `runKontrolDeployment` function of [`KontrolDeployment`](./deployment/KontrolDeployment.sol) partially reproduces the deployment process laid out in the `_run` function of [`Deploy.s.sol`](../../scripts/deploy/Deploy.s.sol). `runKontrolDeployment` has the `stateDiff` modifier to make use of [Foundry's state diff cheatcodes](https://book.getfoundry.sh/cheatcodes/start-state-diff-recording). Kontrol utilizes the JSON resulting from this modifier for two purposes: 1. Load all the state updates generated by `runKontrolDeployment` as the initial configuration for all proofs, effectively offloading the computation of the deployment process to `forge` and thus improving performance. 2. Produce the [`DeploymentSummary`](./proofs/utils/DeploymentSummary.sol) script contract to test that the produced JSON contains correct updates.
diff --git OP/packages/contracts-bedrock/test/setup/CommonTest.sol CELO/packages/contracts-bedrock/test/setup/CommonTest.sol index 86ed109fd7b3f7d7e18b4fcac107cf3eff085f6f..d80cd31f260370bd2299738c95608f44fe547510 100644 --- OP/packages/contracts-bedrock/test/setup/CommonTest.sol +++ CELO/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -6,7 +6,7 @@ import { Setup } from "test/setup/Setup.sol"; import { Events } from "test/setup/Events.sol"; import { FFIInterface } from "test/setup/FFIInterface.sol"; import { Constants } from "src/libraries/Constants.sol"; -import "scripts/DeployConfig.s.sol"; +import "scripts/deploy/DeployConfig.s.sol";   /// @title CommonTest /// @dev An extenstion to `Test` that sets up the optimism smart contracts.
diff --git OP/packages/contracts-bedrock/test/setup/Setup.sol CELO/packages/contracts-bedrock/test/setup/Setup.sol index 3193ce80e6e6c2c865801c3aa98d1256571fb0fb..7cf4d2a47d08f4ff6d4b1bf334fd7b24e0f62277 100644 --- OP/packages/contracts-bedrock/test/setup/Setup.sol +++ CELO/packages/contracts-bedrock/test/setup/Setup.sol @@ -24,9 +24,9 @@ import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; -import { DeployConfig } from "scripts/DeployConfig.s.sol"; +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; +import { Deploy } from "scripts/deploy/Deploy.s.sol"; import { Fork, LATEST_FORK } from "scripts/Config.sol"; -import { Deploy } from "scripts/Deploy.s.sol"; import { L2Genesis, L1Dependencies } from "scripts/L2Genesis.s.sol"; import { OutputMode, Fork, ForkUtils } from "scripts/Config.sol"; import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
diff --git OP/packages/contracts-bedrock/test/vendor/Initializable.t.sol CELO/packages/contracts-bedrock/test/vendor/Initializable.t.sol index 349bdaef6023463764b8eaae1d7396be422738c4..226e4c4b0253f4d822d2768fed20c44bbc7524af 100644 --- OP/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ CELO/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -13,7 +13,7 @@ import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol"; import { Process } from "scripts/libraries/Process.sol"; import "src/L1/ProtocolVersions.sol"; import "src/dispute/lib/Types.sol"; -import "scripts/Deployer.sol"; +import "scripts/deploy/Deployer.sol";   /// @title Initializer_Test /// @dev Ensures that the `initialize()` function on contracts cannot be called more than @@ -129,7 +129,7 @@ // OptimismPortalImpl contracts.push( InitializeableContract({ target: deploy.mustGetAddress("OptimismPortal"), - initCalldata: abi.encodeCall(optimismPortal.initialize, (l2OutputOracle, systemConfig, superchainConfig)), + initCalldata: abi.encodeCall(optimismPortal.initialize, (l2OutputOracle, systemConfig, superchainConfig, 0)), initializedSlotVal: deploy.loadInitializedSlot("OptimismPortal") }) ); @@ -153,7 +153,7 @@ // OptimismPortalProxy contracts.push( InitializeableContract({ target: address(optimismPortal), - initCalldata: abi.encodeCall(optimismPortal.initialize, (l2OutputOracle, systemConfig, superchainConfig)), + initCalldata: abi.encodeCall(optimismPortal.initialize, (l2OutputOracle, systemConfig, superchainConfig, 0)), initializedSlotVal: deploy.loadInitializedSlot("OptimismPortalProxy") }) );
diff --git OP/packages/contracts-bedrock/tsconfig.build.json CELO/packages/contracts-bedrock/tsconfig.build.json deleted file mode 100644 index e5df8a66de3182d1c71103da1bf42f7ea3e8b4f8..0000000000000000000000000000000000000000 --- OP/packages/contracts-bedrock/tsconfig.build.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "outDir": "./dist" - }, - "include": ["src/**/*"] -}
diff --git OP/packages/contracts-bedrock/tsconfig.json CELO/packages/contracts-bedrock/tsconfig.json index 5091614ef561939cc6cd5f5cdc788d76413c82bd..7c7a62708773da00a9c837cdce771d7823e4af87 100644 --- OP/packages/contracts-bedrock/tsconfig.json +++ CELO/packages/contracts-bedrock/tsconfig.json @@ -1,8 +1,27 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { - "outDir": "./dist" + "outDir": "./dist", + "skipLibCheck": true, + "module": "commonjs", + "target": "es2017", + "sourceMap": true, + "esModuleInterop": true, + "composite": true, + "resolveJsonModule": true, + "declaration": true, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "typeRoots": [ + "node_modules/@types" + ] }, + "exclude": [ + "node_modules", + "dist" + ], "include": [ "deploy-config/**/*", "deploy-config/**/*.json",
diff --git OP/packages/chain-mon/.env.example CELO/packages/chain-mon/.env.example index 0e10b0aaf530f07a255be3237405b3a558a455ac..07fbfc9856936f3acdb0a008e0c414fc16c9128e 100644 --- OP/packages/chain-mon/.env.example +++ CELO/packages/chain-mon/.env.example @@ -32,16 +32,6 @@ # Defaults to the first bedrock block if unset. WALLET_MON__START_BLOCK_NUMBER=   ############################################################################### -# ↓ drippie-mon ↓ # -############################################################################### - -# RPC pointing to network where Drippie is deployed -DRIPPIE_MON__RPC= - -# Address of the Drippie contract -DRIPPIE_MON__DRIPPIE_ADDRESS= - -############################################################################### # ↓ wd-mon ↓ # ###############################################################################
diff --git OP/packages/chain-mon/README.md CELO/packages/chain-mon/README.md index 2a7a74c72eb98fd28f994eff7965a826329e6b93..28ab6ffdf4ee1ca10479f6f20c79420fb085a067 100644 --- OP/packages/chain-mon/README.md +++ CELO/packages/chain-mon/README.md @@ -23,8 +23,8 @@ ``` pnpm start:<service name> ```   -For example, to run `drippie-mon`, execute: +For example, to run `balance-mon`, execute:   ``` -pnpm start:drippie-mon +pnpm start:balance-mon ```
diff --git OP/packages/chain-mon/package.json CELO/packages/chain-mon/package.json index 456639c9ccdaef41ffd718a1452ac55cb0a3d74b..9ff9bf353382f04dc272b8b3b056c00e5b232ce3 100644 --- OP/packages/chain-mon/package.json +++ CELO/packages/chain-mon/package.json @@ -10,7 +10,6 @@ "dist/*" ], "scripts": { "dev:balance-mon": "tsx watch ./internal/balance-mon/service.ts", - "dev:drippie-mon": "tsx watch ./contrib/drippie/service.ts", "dev:fault-mon": "tsx watch ./src/fault-mon/service.ts", "dev:multisig-mon": "tsx watch ./internal/multisig-mon/service.ts", "dev:replica-mon": "tsx watch ./contrib/replica-mon/service.ts", @@ -19,7 +18,6 @@ "dev:wd-mon": "tsx watch ./src/wd-mon/service.ts", "dev:faultproof-wd-mon": "tsx ./src/faultproof-wd-mon/service.ts", "dev:initialized-upgraded-mon": "tsx watch ./contrib/initialized-upgraded-mon/service.ts", "start:balance-mon": "tsx ./internal/balance-mon/service.ts", - "start:drippie-mon": "tsx ./contrib/drippie/service.ts", "start:fault-mon": "tsx ./src/fault-mon/service.ts", "start:multisig-mon": "tsx ./internal/multisig-mon/service.ts", "start:replica-mon": "tsx ./contrib/replica-mon/service.ts",
diff --git OP/packages/chain-mon/tsconfig.json CELO/packages/chain-mon/tsconfig.json index f9bea541e657224b2de8265af0423a970af46dff..46e4d8fe12ca94f71b236a13922fd33ec4a591e2 100644 --- OP/packages/chain-mon/tsconfig.json +++ CELO/packages/chain-mon/tsconfig.json @@ -1,9 +1,27 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./dist", - "skipLibCheck": true + "skipLibCheck": true, + "module": "commonjs", + "target": "es2017", + "sourceMap": true, + "esModuleInterop": true, + "composite": true, + "resolveJsonModule": true, + "declaration": true, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "typeRoots": [ + "node_modules/@types" + ] }, + "exclude": [ + "node_modules", + "dist" + ], "include": [ "package.json", "src/abi/IGnosisSafe.0.8.19.json",
diff --git OP/op-chain-ops/Dockerfile CELO/op-chain-ops/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..532a73bb5acbbae5c99f47cf60c9fb5a64c6a69a --- /dev/null +++ CELO/op-chain-ops/Dockerfile @@ -0,0 +1,29 @@ +FROM golang:1.21.1-alpine3.18 as builder + +RUN apk --no-cache add make + +COPY ./go.mod /app/go.mod +COPY ./go.sum /app/go.sum + +WORKDIR /app + +RUN go mod download + +COPY ./op-service /app/op-service +COPY ./op-node /app/op-node +COPY ./op-plasma /app/op-plasma +COPY ./op-chain-ops /app/op-chain-ops +WORKDIR /app/op-chain-ops +RUN make celo-migrate + +FROM alpine:3.18 +RUN apk --no-cache add ca-certificates bash rsync + +# RUN addgroup -S app && adduser -S app -G app +# USER app +WORKDIR /app + +COPY --from=builder /app/op-chain-ops/bin/celo-migrate /app +ENV PATH="/app:${PATH}" + +ENTRYPOINT ["/app/celo-migrate"]
diff --git OP/op-chain-ops/Makefile CELO/op-chain-ops/Makefile index 1808807c73781785d406aa674659b07a91b84942..d93243a215226aa24247b0928a60f7e2c994249d 100644 --- OP/op-chain-ops/Makefile +++ CELO/op-chain-ops/Makefile @@ -3,9 +3,6 @@ ifeq ($(shell uname),Darwin) FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic endif   -op-version-check: - go build -o ./bin/op-version-check ./cmd/op-version-check/main.go - ecotone-scalar: go build -o ./bin/ecotone-scalar ./cmd/ecotone-scalar/main.go   @@ -14,6 +11,9 @@ go build -o ./bin/receipt-reference-builder ./cmd/receipt-reference-builder/*.go   op-upgrade: go build -o ./bin/op-upgrade ./cmd/op-upgrade/main.go + +celo-migrate: + go build -o ./bin/celo-migrate ./cmd/celo-migrate/*.go   test: go test ./...
diff --git OP/op-chain-ops/README.md CELO/op-chain-ops/README.md deleted file mode 100644 index a40232fe7d17a784e9d60710d6097d6217b99045..0000000000000000000000000000000000000000 --- OP/op-chain-ops/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# op-chain-ops - -This package contains utilities for working with chain state. - -## op-version-check - -A CLI tool for determining which contract versions are deployed for -chains in a superchain. It will output a JSON file that contains a -list of each chain's versions. It is assumed that the implementations -that are being checked have already been deployed and their contract -addresses exist inside of the `superchain-registry` repository. It is -also assumed that the semantic version file in the `superchain-registry` -has been updated. The tool will output the semantic versioning to -determine which contract versions are deployed. - -### Configuration - -#### L1 RPC URL - -The L1 RPC URL is used to determine which superchain to target. All -L2s that are not based on top of the L1 chain that corresponds to the -L1 RPC URL are filtered out from being checked. It also is used to -double check that the data in the `superchain-registry` is correct. - -#### Chain IDs - -A list of L2 chain IDs can be passed that will be used to filter which -L2 chains will have their versions checked. Omitting this argument will -result in all chains in the superchain being considered. - -#### Deploy Config - -The path to the `deploy-config` directory in the contracts package. -Since multiple L2 networks may be considered in the check, the `deploy-config` -directory must be passed and then the particular deploy config files will -be read out of the directory as needed. - -#### Outfile - -The file that the versions should be written to. If omitted, the file -will be written to stdout - -#### Usage - -It can be built and run using the [Makefile](./Makefile) `op-version-check` -target. Run `make op-version-check` to create a binary in [./bin/op-version-check](./bin/op-version-check) -that can be executed, optionally providing the `--l1-rpc-url`, `--chain-ids`, -`--superchain-target`, and `--outfile` flags. - -```sh -./bin/op-version-check -```
diff --git OP/op-chain-ops/clients/clients.go CELO/op-chain-ops/clients/clients.go index 475a130a1f031cf8ab042dfa04367bd32e53e363..16cf1970728c652499250a1f10091af315ac2c35 100644 --- OP/op-chain-ops/clients/clients.go +++ CELO/op-chain-ops/clients/clients.go @@ -10,7 +10,7 @@ "github.com/ethereum/go-ethereum/rpc" "github.com/urfave/cli/v2" )   -// clients represents a set of initialized RPC clients +// Clients represents a set of initialized RPC clients type Clients struct { L1Client *ethclient.Client L2Client *ethclient.Client
diff --git OP/op-chain-ops/cmd/celo-migrate/README.md CELO/op-chain-ops/cmd/celo-migrate/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a4d26bfabe473e2efd821b2f076349bd8b19677d --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/README.md @@ -0,0 +1,136 @@ +# Celo L2 Migration Script + +## Overview + +This script migrates a Celo L1 database (old datadir) into a new database compatible with Celo L2 (new datadir). It consists of 3 main processes that respectively migrate ancient blocks, non-ancient blocks and state. Migrated data is copied into a new datadir, leaving the old datadir unchanged. + +To minimize migration downtime, the script is designed to run in two stages: +1. The `pre migration` stage can be run ahead of the `full migration` and will process as much of the migration as possible up to that point. +2. The `full migration` can then be run to finish migrating new blocks that were created after the `pre migration` and apply necessary state changes on top of the migration block. + +### Pre migration + +The `pre migration` consists of two parts that are run in parallel: +- Copy and transform the ancient / frozen blocks (i.e. all blocks before the last 90000). +- Copy over the rest of the database using `rsync`. + +The ancients db is migrated sequentially because it is append-only, while the rest of the database is copied and then transformed in-place. We use `rsync` because it has flags for ignoring the ancients directory, skipping any already copied files and deleting any extra files in the new db, ensuring that we can run the script multiple times and only copy over actual updates. + +The `pre migration` step is still run during a `full migration` but it will be much quicker as only newly frozen blocks and recent file changes need to be migrated. + +### Full migration + +During the `full migration`, we re-run the `pre migration` step to capture any updates since the last `pre migration` and then apply in-place changes to non-ancient blocks and state. While this is happening, the script also checks for any stray ancient blocks that have remained in leveldb despite being frozen and removes them from the new db. Non-ancient blocks are then transformed to ensure compatibility with the L2 codebase. + +Finally after all blocks have been migrated, the script performs a series of modifications to the state db: +1. First, it deploys the L2 smart contracts by iterating through the genesis allocs passed to the script and setting the nonce, balance, code and storage for each address accordingly, overwritting existing data if necessary. +2. Finally, these changes are committed to the state db to produce a new state root and create the first Celo L2 block. + +### Notes + +> [!TIP] +> See `--help` for how to run each portion of the script individually, along with other configuration options. + +The longest running section of the script is the ancients migration, followed by the `rsync` command. By running these together in a `pre migration` we greatly reduce how long they will take during the `full migration`. Changes made to non-ancient blocks and state during a `full migration` are erased by the next `rsync` command. + +The script outputs a `rollup-config.json` file that is passed to the sequencer in order to start the L2 network. + +### Running the script + +> [!NOTE] +> You will need `rsync` to run this script if it's not already installed. + +From the `op-chain-ops` directory, first build the script by running: + +```bash +make celo-migrate +``` + +You can then run the script as follows: + +```bash +go run ./cmd/celo-migrate --help +``` + +#### Running with local test setup (Alfajores / Holesky) + +To test the script locally, we can migrate an Alfajores database and use Holesky as our L1. The input files needed for this can be found in `./testdata`. The necessary smart contracts have already been deployed on Holesky. + +##### Pull down the latest Alfajores database snapshot + +```bash +gcloud alpha storage cp gs://celo-chain-backup/alfajores/chaindata-latest.tar.zst alfajores.tar.zst +``` + +Unzip and rename + +```bash +tar --use-compress-program=unzstd -xvf alfajores.tar.zst +mv chaindata ./data/alfajores_old +``` + +##### Generate test allocs file + +The state migration takes in an allocs file that specifies the l2 state changes to be made during the migration. This file can be generated from the deploy config and l1 contract addresses by running the following from the `contracts-bedrock` directory. + +```bash +CONTRACT_ADDRESSES_PATH=../../op-chain-ops/cmd/celo-migrate/testdata/deployment-l1-holesky.json \ +DEPLOY_CONFIG_PATH=../../op-chain-ops/cmd/celo-migrate/testdata/deploy-config-holesky-alfajores.json \ +STATE_DUMP_PATH=../../op-chain-ops/cmd/celo-migrate/testdata/l2-allocs-alfajores.json \ +forge script ./scripts/L2Genesis.s.sol:L2Genesis \ +--sig 'runWithStateDump()' +``` + +This should output the allocs file to `./testdata/l2-allocs-alfajores.json`. If you encounter difficulties with this and want to just continue testing the script, you can alternatively find the allocs file [here](https://gist.github.com/jcortejoso/7f90ba9b67c669791014661ccb6de81a). + +##### Run script with test configuration + +```bash +go run ./cmd/celo-migrate pre \ +--old-db ./data/alfajores_old \ +--new-db ./data/alfajores_new +``` + +Running the pre-migration script should take ~5 minutes. This script copies and transforms ancient blocks and, in parallel, copies over all other chaindata without transforming it. This can be re-run mutliple times leading up to the full migration, and should only migrate updates to the old db between re-runs. + +```bash +go run ./cmd/celo-migrate full \ +--deploy-config ./cmd/celo-migrate/testdata/deploy-config-holesky-alfajores.json \ +--l1-deployments ./cmd/celo-migrate/testdata/deployment-l1-holesky.json \ +--l1-rpc https://ethereum-holesky-rpc.publicnode.com \ +--l2-allocs ./cmd/celo-migrate/testdata/l2-allocs-alfajores.json \ +--outfile.rollup-config ./cmd/celo-migrate/testdata/rollup-config.json \ +--old-db ./data/alfajores_old \ +--new-db ./data/alfajores_new +``` + +Running the full migration script re-runs the pre-migration script once to migrate any new changes to the old db that have occurred since the last pre-migration. It then performs in-place transformations on the non-ancient blocks and performs the state migration as well. + +#### Running for Cel2 migration + +##### Generate allocs file + +You can generate the allocs file needed to run the migration with the following script in `contracts-bedrock` + +```bash +CONTRACT_ADDRESSES_PATH=<PATH_TO_CONTRACT_ADDRESSES> \ +DEPLOY_CONFIG_PATH=<PATH_TO_MY_DEPLOY_CONFIG> \ +STATE_DUMP_PATH=<PATH_TO_WRITE_L2_ALLOCS> \ +forge script scripts/L2Genesis.s.sol:L2Genesis \ +--sig 'runWithStateDump()' +``` + +##### Dry-run / pre-migration + +To minimize downtime caused by the migration, node operators can prepare their Cel2 databases by running the pre-migration command a day ahead of the actual migration. This will pre-populate the new database with most of the ancient blocks needed for the final migration and copy over other chaindata without transforming it. + +If node operators would like to practice a `full migration` they can do so and reset their databases to the correct state by running another `pre migration` afterward. + +> [!IMPORTANT] +> The pre-migration should be run using a chaindata snapshot, rather than a db that is being used by a node. To avoid network downtime, we recommend that node operators do not stop any nodes in order to perform the pre-migration. + +Node operators should inspect their migration logs after the dry-run to ensure the migration completed succesfully and direct any questions to the Celo developer community on Discord before the actual migration. + +##### Final migration + +On the day of the actual Cel2 migration, the `full migration` script can be run using the datadir of a Celo L1 node that has halted on the migration block. Far in advance of the migration, a version of `celo-blockchain` will be distributed where a flag can specify a block to halt on. When the Celo community aligns on a migration block, node operators will start / restart their nodes with this flag specifying the migration block. Their nodes will halt when this block is reached, at which point they will be able to run `full migration` and begin syncing with the Celo L2 network.
diff --git OP/op-chain-ops/cmd/celo-migrate/ancients.go CELO/op-chain-ops/cmd/celo-migrate/ancients.go new file mode 100644 index 0000000000000000000000000000000000000000..8e3d43a7fdf2f30181d1dd701a4e3413df0fbfa1 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/ancients.go @@ -0,0 +1,216 @@ +package main + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "golang.org/x/sync/errgroup" +) + +// RLPBlockRange is a range of blocks in RLP format +type RLPBlockRange struct { + start uint64 + hashes [][]byte + headers [][]byte + bodies [][]byte + receipts [][]byte + tds [][]byte +} + +func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSize, bufferSize uint64) (uint64, uint64, error) { + defer timer("ancients")() + + oldFreezer, err := rawdb.NewChainFreezer(filepath.Join(oldDBPath, "ancient"), "", false) // Can't be readonly because we need the .meta files to be created + if err != nil { + return 0, 0, fmt.Errorf("failed to open old freezer: %w", err) + } + defer oldFreezer.Close() + + newFreezer, err := rawdb.NewChainFreezer(filepath.Join(newDBPath, "ancient"), "", false) + if err != nil { + return 0, 0, fmt.Errorf("failed to open new freezer: %w", err) + } + defer newFreezer.Close() + + numAncientsOld, err := oldFreezer.Ancients() + if err != nil { + return 0, 0, fmt.Errorf("failed to get number of ancients in old freezer: %w", err) + } + + numAncientsNewBefore, err := newFreezer.Ancients() + if err != nil { + return 0, 0, fmt.Errorf("failed to get number of ancients in new freezer: %w", err) + } + + if numAncientsNewBefore >= numAncientsOld { + log.Info("Ancient Block Migration Skipped", "process", "ancients", "ancientsInOldDB", numAncientsOld, "ancientsInNewDB", numAncientsNewBefore) + return numAncientsNewBefore, numAncientsNewBefore, nil + } + + log.Info("Ancient Block Migration Started", "process", "ancients", "startBlock", numAncientsNewBefore, "endBlock", numAncientsOld-1, "count", numAncientsOld-numAncientsNewBefore, "step", batchSize) + + g, ctx := errgroup.WithContext(ctx) + readChan := make(chan RLPBlockRange, bufferSize) + transformChan := make(chan RLPBlockRange, bufferSize) + + g.Go(func() error { + return readAncientBlocks(ctx, oldFreezer, numAncientsNewBefore, numAncientsOld, batchSize, readChan) + }) + g.Go(func() error { return transformBlocks(ctx, readChan, transformChan) }) + g.Go(func() error { return writeAncientBlocks(ctx, newFreezer, transformChan, numAncientsOld) }) + + if err = g.Wait(); err != nil { + return 0, 0, fmt.Errorf("failed to migrate ancients: %w", err) + } + + numAncientsNewAfter, err := newFreezer.Ancients() + if err != nil { + return 0, 0, fmt.Errorf("failed to get number of ancients in new freezer: %w", err) + } + + log.Info("Ancient Block Migration Ended", "process", "ancients", "ancientsInOldDB", numAncientsOld, "ancientsInNewDB", numAncientsNewAfter, "migrated", numAncientsNewAfter-numAncientsNewBefore) + return numAncientsNewBefore, numAncientsNewAfter, nil +} + +func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, endBlock, batchSize uint64, out chan<- RLPBlockRange) error { + defer close(out) + + for i := startBlock; i < endBlock; i += batchSize { + select { + case <-ctx.Done(): + return ctx.Err() + default: + count := min(batchSize, endBlock-i+1) + start := i + + blockRange := RLPBlockRange{ + start: start, + hashes: make([][]byte, count), + headers: make([][]byte, count), + bodies: make([][]byte, count), + receipts: make([][]byte, count), + tds: make([][]byte, count), + } + var err error + + blockRange.hashes, err = freezer.AncientRange(rawdb.ChainFreezerHashTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read hashes from old freezer: %w", err) + } + blockRange.headers, err = freezer.AncientRange(rawdb.ChainFreezerHeaderTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read headers from old freezer: %w", err) + } + blockRange.bodies, err = freezer.AncientRange(rawdb.ChainFreezerBodiesTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read bodies from old freezer: %w", err) + } + blockRange.receipts, err = freezer.AncientRange(rawdb.ChainFreezerReceiptTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read receipts from old freezer: %w", err) + } + blockRange.tds, err = freezer.AncientRange(rawdb.ChainFreezerDifficultyTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read tds from old freezer: %w", err) + } + + out <- blockRange + } + } + return nil +} + +func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RLPBlockRange) error { + // Transform blocks from the in channel and send them to the out channel + defer close(out) + for blockRange := range in { + select { + case <-ctx.Done(): + return ctx.Err() + default: + for i := range blockRange.hashes { + blockNumber := blockRange.start + uint64(i) + + newHeader, err := transformHeader(blockRange.headers[i]) + if err != nil { + return fmt.Errorf("can't transform header: %w", err) + } + newBody, err := transformBlockBody(blockRange.bodies[i]) + if err != nil { + return fmt.Errorf("can't transform body: %w", err) + } + + if yes, newHash := hasSameHash(newHeader, blockRange.hashes[i]); !yes { + log.Error("Hash mismatch", "block", blockNumber, "oldHash", common.BytesToHash(blockRange.hashes[i]), "newHash", newHash) + return fmt.Errorf("hash mismatch at block %d", blockNumber) + } + + blockRange.headers[i] = newHeader + blockRange.bodies[i] = newBody + } + out <- blockRange + } + } + return nil +} + +func writeAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, in <-chan RLPBlockRange, totalAncientBlocks uint64) error { + // Write blocks from the in channel to the newDb + for blockRange := range in { + select { + case <-ctx.Done(): + return ctx.Err() + default: + _, err := freezer.ModifyAncients(func(aWriter ethdb.AncientWriteOp) error { + for i := range blockRange.hashes { + blockNumber := blockRange.start + uint64(i) + if err := aWriter.AppendRaw(rawdb.ChainFreezerHashTable, blockNumber, blockRange.hashes[i]); err != nil { + return fmt.Errorf("can't write hash to Freezer: %w", err) + } + if err := aWriter.AppendRaw(rawdb.ChainFreezerHeaderTable, blockNumber, blockRange.headers[i]); err != nil { + return fmt.Errorf("can't write header to Freezer: %w", err) + } + if err := aWriter.AppendRaw(rawdb.ChainFreezerBodiesTable, blockNumber, blockRange.bodies[i]); err != nil { + return fmt.Errorf("can't write body to Freezer: %w", err) + } + if err := aWriter.AppendRaw(rawdb.ChainFreezerReceiptTable, blockNumber, blockRange.receipts[i]); err != nil { + return fmt.Errorf("can't write receipts to Freezer: %w", err) + } + if err := aWriter.AppendRaw(rawdb.ChainFreezerDifficultyTable, blockNumber, blockRange.tds[i]); err != nil { + return fmt.Errorf("can't write td to Freezer: %w", err) + } + } + return nil + }) + if err != nil { + return fmt.Errorf("failed to write block range: %w", err) + } + blockRangeEnd := blockRange.start + uint64(len(blockRange.hashes)) - 1 + log.Info("Wrote ancient blocks", "start", blockRange.start, "end", blockRangeEnd, "count", len(blockRange.hashes), "remaining", totalAncientBlocks-blockRangeEnd) + } + } + return nil +} + +// getStrayAncientBlocks returns a list of ancient block numbers / hashes that somehow were not removed from leveldb +func getStrayAncientBlocks(dbPath string) ([]*rawdb.NumberHash, error) { + defer timer("getStrayAncientBlocks")() + + db, err := openDB(dbPath, true) + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) + } + defer db.Close() + + numAncients, err := db.Ancients() + if err != nil { + return nil, fmt.Errorf("failed to get number of ancients in database: %w", err) + } + + return rawdb.ReadAllHashesInRange(db, 1, numAncients-1), nil +}
diff --git OP/op-chain-ops/cmd/celo-migrate/db.go CELO/op-chain-ops/cmd/celo-migrate/db.go new file mode 100644 index 0000000000000000000000000000000000000000..38872028d3be3c051981d479de75c2fc8af59d58 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/db.go @@ -0,0 +1,100 @@ +package main + +import ( + "encoding/binary" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// Constants for the database +const ( + DBCache = 1024 // size of the cache in MB + DBHandles = 60 // number of handles +) + +var ( + headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header +) + +// encodeBlockNumber encodes a block number as big endian uint64 +func encodeBlockNumber(number uint64) []byte { + enc := make([]byte, 8) + binary.BigEndian.PutUint64(enc, number) + return enc +} + +// headerKey = headerPrefix + num (uint64 big endian) + hash +func headerKey(number uint64, hash common.Hash) []byte { + return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...) +} + +// Opens a database with access to AncientsDb +func openDB(chaindataPath string, readOnly bool) (ethdb.Database, error) { + if _, err := os.Stat(chaindataPath); errors.Is(err, os.ErrNotExist) { + return nil, err + } + + db, err := rawdb.Open(rawdb.OpenOptions{ + Type: "leveldb", + Directory: chaindataPath, + AncientsDirectory: filepath.Join(chaindataPath, "ancient"), + Namespace: "", + Cache: DBCache, + Handles: DBHandles, + ReadOnly: readOnly, + }) + if err != nil { + return nil, err + } + + return db, nil +} + +// Opens a database without access to AncientsDb +func openDBWithoutFreezer(chaindataPath string, readOnly bool) (ethdb.Database, error) { + if _, err := os.Stat(chaindataPath); errors.Is(err, os.ErrNotExist) { + return nil, err + } + + newDB, err := rawdb.NewLevelDBDatabase(chaindataPath, DBCache, DBHandles, "", readOnly) + if err != nil { + return nil, err + } + + return newDB, nil +} + +func createNewDbPathIfNotExists(newDBPath string) error { + if err := os.MkdirAll(newDBPath, 0755); err != nil { + return fmt.Errorf("failed to create new database directory: %w", err) + } + return nil +} + +func removeBlocks(ldb ethdb.Database, numberHashes []*rawdb.NumberHash) error { + defer timer("removeBlocks")() + + if len(numberHashes) == 0 { + return nil + } + + batch := ldb.NewBatch() + + for _, numberHash := range numberHashes { + log.Debug("Removing block", "block", numberHash.Number) + rawdb.DeleteBlockWithoutNumber(batch, numberHash.Hash, numberHash.Number) + rawdb.DeleteCanonicalHash(batch, numberHash.Number) + } + if err := batch.Write(); err != nil { + log.Error("Failed to write batch", "error", err) + } + + return nil +}
diff --git OP/op-chain-ops/cmd/celo-migrate/main.go CELO/op-chain-ops/cmd/celo-migrate/main.go new file mode 100644 index 0000000000000000000000000000000000000000..baac933ff3cdc700f7eb50222e72261ef0e49d67 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/main.go @@ -0,0 +1,412 @@ +package main + +import ( + "context" + "errors" + "fmt" + "math/big" + "os" + "os/exec" + "runtime/debug" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/mattn/go-isatty" + "github.com/urfave/cli/v2" + "golang.org/x/exp/slog" + "golang.org/x/sync/errgroup" +) + +var ( + deployConfigFlag = &cli.PathFlag{ + Name: "deploy-config", + Usage: "Path to the JSON file that was used for the l1 contracts deployment. A test example can be found here 'op-chain-ops/genesis/testdata/test-deploy-config-full.json' and documentation for the fields is at https://docs.optimism.io/builders/chain-operators/management/configuration", + Required: true, + } + l1DeploymentsFlag = &cli.PathFlag{ + Name: "l1-deployments", + Usage: "Path to L1 deployments JSON file, the output of running the bedrock contracts deployment for the given 'deploy-config'", + Required: true, + } + l1RPCFlag = &cli.StringFlag{ + Name: "l1-rpc", + Usage: "RPC URL for a node of the L1 defined in the 'deploy-config'", + Required: true, + } + l2AllocsFlag = &cli.PathFlag{ + Name: "l2-allocs", + Usage: "Path to L2 genesis allocs file. You can find instructions on how to generate this file in the README", + Required: true, + } + outfileRollupConfigFlag = &cli.PathFlag{ + Name: "outfile.rollup-config", + Usage: "Path to write the rollup config JSON file, to be provided to op-node with the 'rollup.config' flag", + Required: true, + } + migrationBlockTimeFlag = &cli.Uint64Flag{ + Name: "migration-block-time", + Usage: "Specifies a unix timestamp to use for the migration block. If not provided, the current time will be used.", + } + oldDBPathFlag = &cli.PathFlag{ + Name: "old-db", + Usage: "Path to the old Celo chaindata dir, can be found at '<datadir>/celo/chaindata'", + Required: true, + } + newDBPathFlag = &cli.PathFlag{ + Name: "new-db", + Usage: "Path to write migrated Celo chaindata, note the new node implementation expects to find this chaindata at the following path '<datadir>/geth/chaindata", + Required: true, + } + batchSizeFlag = &cli.Uint64Flag{ + Name: "batch-size", + Usage: "Batch size to use for block migration, larger batch sizes can speed up migration but require more memory. If increasing the batch size consider also increasing the memory-limit", + Value: 50000, // TODO(Alec) optimize default parameters + } + bufferSizeFlag = &cli.Uint64Flag{ + Name: "buffer-size", + Usage: "Buffer size to use for ancient block migration channels. Defaults to 0. Included to facilitate testing for performance improvements.", + Value: 0, + } + memoryLimitFlag = &cli.Int64Flag{ + Name: "memory-limit", + Usage: "Memory limit in MiB, should be set lower than the available amount of memory in your system to prevent out of memory errors", + Value: 7500, + } + + preMigrationFlags = []cli.Flag{ + oldDBPathFlag, + newDBPathFlag, + batchSizeFlag, + bufferSizeFlag, + memoryLimitFlag, + } + fullMigrationFlags = append( + preMigrationFlags, + deployConfigFlag, + l1DeploymentsFlag, + l1RPCFlag, + l2AllocsFlag, + outfileRollupConfigFlag, + migrationBlockTimeFlag, + ) +) + +type preMigrationOptions struct { + oldDBPath string + newDBPath string + batchSize uint64 + bufferSize uint64 + memoryLimit int64 +} + +type stateMigrationOptions struct { + deployConfig string + l1Deployments string + l1RPC string + l2AllocsPath string + outfileRollupConfig string + migrationBlockTime uint64 +} + +type fullMigrationOptions struct { + preMigrationOptions + stateMigrationOptions +} + +func parsePreMigrationOptions(ctx *cli.Context) preMigrationOptions { + return preMigrationOptions{ + oldDBPath: ctx.String(oldDBPathFlag.Name), + newDBPath: ctx.String(newDBPathFlag.Name), + batchSize: ctx.Uint64(batchSizeFlag.Name), + bufferSize: ctx.Uint64(bufferSizeFlag.Name), + memoryLimit: ctx.Int64(memoryLimitFlag.Name), + } +} + +func parseStateMigrationOptions(ctx *cli.Context) stateMigrationOptions { + return stateMigrationOptions{ + deployConfig: ctx.Path(deployConfigFlag.Name), + l1Deployments: ctx.Path(l1DeploymentsFlag.Name), + l1RPC: ctx.String(l1RPCFlag.Name), + l2AllocsPath: ctx.Path(l2AllocsFlag.Name), + outfileRollupConfig: ctx.Path(outfileRollupConfigFlag.Name), + migrationBlockTime: ctx.Uint64(migrationBlockTimeFlag.Name), + } +} + +func parseFullMigrationOptions(ctx *cli.Context) fullMigrationOptions { + return fullMigrationOptions{ + preMigrationOptions: parsePreMigrationOptions(ctx), + stateMigrationOptions: parseStateMigrationOptions(ctx), + } +} + +func main() { + + color := isatty.IsTerminal(os.Stderr.Fd()) + handler := log.NewTerminalHandlerWithLevel(os.Stderr, slog.LevelInfo, color) + oplog.SetGlobalLogHandler(handler) + + app := &cli.App{ + Name: "celo-migrate", + Usage: "Migrate Celo block and state data to a CeL2 DB", + Commands: []*cli.Command{ + { + Name: "pre", + Usage: "Perform a pre-migration of ancient blocks and copy over all other data without transforming it. This should be run a day before the full migration command is run to minimize downtime.", + Flags: preMigrationFlags, + Action: func(ctx *cli.Context) error { + if _, _, err := runPreMigration(parsePreMigrationOptions(ctx)); err != nil { + return fmt.Errorf("failed to run pre-migration: %w", err) + } + log.Info("Finished pre migration successfully!") + return nil + }, + }, + { + Name: "full", + Usage: "Perform a full migration of both block and state data to a CeL2 DB", + Flags: fullMigrationFlags, + Action: func(ctx *cli.Context) error { + if err := runFullMigration(parseFullMigrationOptions(ctx)); err != nil { + return fmt.Errorf("failed to run full migration: %w", err) + } + log.Info("Finished full migration successfully!") + return nil + }, + }, + }, + OnUsageError: func(ctx *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + _ = cli.ShowAppHelp(ctx) + return fmt.Errorf("please provide a valid command") + }, + } + + if err := app.Run(os.Args); err != nil { + log.Crit("error in migration", "err", err) + } +} + +func runFullMigration(opts fullMigrationOptions) error { + defer timer("full migration")() + + log.Info("Full Migration Started", "oldDBPath", opts.oldDBPath, "newDBPath", opts.newDBPath) + + var err error + var numAncients uint64 + var strayAncientBlocks []*rawdb.NumberHash + + if strayAncientBlocks, numAncients, err = runPreMigration(opts.preMigrationOptions); err != nil { + return fmt.Errorf("failed to run pre-migration: %w", err) + } + + if err = runNonAncientMigration(opts.newDBPath, strayAncientBlocks, opts.batchSize, numAncients); err != nil { + return fmt.Errorf("failed to run non-ancient migration: %w", err) + } + if err := runStateMigration(opts.newDBPath, opts.stateMigrationOptions); err != nil { + return fmt.Errorf("failed to run state migration: %w", err) + } + + log.Info("Full Migration Finished", "oldDBPath", opts.oldDBPath, "newDBPath", opts.newDBPath) + + return nil +} + +func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, error) { + defer timer("pre-migration")() + + log.Info("Pre-Migration Started", "oldDBPath", opts.oldDBPath, "newDBPath", opts.newDBPath, "batchSize", opts.batchSize, "memoryLimit", opts.memoryLimit) + + // Check that `rsync` command is available. We use this to copy the db excluding ancients, which we will copy separately + if _, err := exec.LookPath("rsync"); err != nil { + return nil, 0, fmt.Errorf("please install `rsync` to run block migration") + } + + debug.SetMemoryLimit(opts.memoryLimit * 1 << 20) // Set memory limit, converting from MiB to bytes + + var err error + + if err = createNewDbPathIfNotExists(opts.newDBPath); err != nil { + return nil, 0, fmt.Errorf("failed to create new db path: %w", err) + } + + var numAncientsNewBefore uint64 + var numAncientsNewAfter uint64 + var strayAncientBlocks []*rawdb.NumberHash + g, ctx := errgroup.WithContext(context.Background()) + g.Go(func() error { + if numAncientsNewBefore, numAncientsNewAfter, err = migrateAncientsDb(ctx, opts.oldDBPath, opts.newDBPath, opts.batchSize, opts.bufferSize); err != nil { + return fmt.Errorf("failed to migrate ancients database: %w", err) + } + return nil + }) + g.Go(func() error { + // By doing this once during the premigration, we get a speedup when we run it again in a full migration. + return copyDbExceptAncients(opts.oldDBPath, opts.newDBPath) + }) + g.Go(func() error { + if strayAncientBlocks, err = getStrayAncientBlocks(opts.oldDBPath); err != nil { + return fmt.Errorf("failed to get stray ancient blocks: %w", err) + } + return nil + }) + + if err = g.Wait(); err != nil { + return nil, 0, fmt.Errorf("failed to migrate blocks: %w", err) + } + + log.Info("Pre-Migration Finished", "oldDBPath", opts.oldDBPath, "newDBPath", opts.newDBPath, "migratedAncients", numAncientsNewAfter-numAncientsNewBefore, "strayAncientBlocks", len(strayAncientBlocks)) + + return strayAncientBlocks, numAncientsNewAfter, nil +} + +func runNonAncientMigration(newDBPath string, strayAncientBlocks []*rawdb.NumberHash, batchSize, numAncients uint64) error { + defer timer("non-ancient migration")() + + newDB, err := openDBWithoutFreezer(newDBPath, false) + if err != nil { + return fmt.Errorf("failed to open new database: %w", err) + } + defer newDB.Close() + + // get the last block number + hash := rawdb.ReadHeadHeaderHash(newDB) + lastBlock := *rawdb.ReadHeaderNumber(newDB, hash) + lastAncient := numAncients - 1 + + log.Info("Non-Ancient Block Migration Started", "process", "non-ancients", "newDBPath", newDBPath, "batchSize", batchSize, "startBlock", numAncients, "endBlock", lastBlock, "count", lastBlock-lastAncient, "lastAncientBlock", lastAncient) + + var numNonAncients uint64 + if numNonAncients, err = migrateNonAncientsDb(newDB, lastBlock, numAncients, batchSize); err != nil { + return fmt.Errorf("failed to migrate non-ancients database: %w", err) + } + + err = removeBlocks(newDB, strayAncientBlocks) + if err != nil { + return fmt.Errorf("failed to remove stray ancient blocks: %w", err) + } + log.Info("Removed stray ancient blocks still in leveldb", "process", "non-ancients", "removedBlocks", len(strayAncientBlocks)) + + log.Info("Non-Ancient Block Migration Completed", "process", "non-ancients", "migratedNonAncients", numNonAncients) + + return nil +} + +func runStateMigration(newDBPath string, opts stateMigrationOptions) error { + defer timer("state migration")() + + log.Info("State Migration Started", "newDBPath", newDBPath, "deployConfig", opts.deployConfig, "l1Deployments", opts.l1Deployments, "l1RPC", opts.l1RPC, "l2AllocsPath", opts.l2AllocsPath, "outfileRollupConfig", opts.outfileRollupConfig) + + // Read deployment configuration + config, err := genesis.NewDeployConfig(opts.deployConfig) + if err != nil { + return err + } + + if config.DeployCeloContracts { + return errors.New("DeployCeloContracts is not supported in migration") + } + if config.FundDevAccounts { + return errors.New("FundDevAccounts is not supported in migration") + } + + // Try reading the L1 deployment information + deployments, err := genesis.NewL1Deployments(opts.l1Deployments) + if err != nil { + return fmt.Errorf("cannot read L1 deployments at %s: %w", opts.l1Deployments, err) + } + config.SetDeployments(deployments) + + // Get latest block information from L1 + var l1StartBlock *types.Block + client, err := ethclient.Dial(opts.l1RPC) + if err != nil { + return fmt.Errorf("cannot dial %s: %w", opts.l1RPC, err) + } + + if config.L1StartingBlockTag == nil { + l1StartBlock, err = client.BlockByNumber(context.Background(), nil) + if err != nil { + return fmt.Errorf("cannot fetch latest block: %w", err) + } + tag := rpc.BlockNumberOrHashWithHash(l1StartBlock.Hash(), true) + config.L1StartingBlockTag = (*genesis.MarshalableRPCBlockNumberOrHash)(&tag) + } else if config.L1StartingBlockTag.BlockHash != nil { + l1StartBlock, err = client.BlockByHash(context.Background(), *config.L1StartingBlockTag.BlockHash) + if err != nil { + return fmt.Errorf("cannot fetch block by hash: %w", err) + } + } else if config.L1StartingBlockTag.BlockNumber != nil { + l1StartBlock, err = client.BlockByNumber(context.Background(), big.NewInt(config.L1StartingBlockTag.BlockNumber.Int64())) + if err != nil { + return fmt.Errorf("cannot fetch block by number: %w", err) + } + } + + // Ensure that there is a starting L1 block + if l1StartBlock == nil { + return fmt.Errorf("no starting L1 block") + } + + // Sanity check the config. Do this after filling in the L1StartingBlockTag + // if it is not defined. + if err := config.Check(); err != nil { + return err + } + + log.Info("Using L1 Start Block", "number", l1StartBlock.Number(), "hash", l1StartBlock.Hash().Hex()) + + // Build the L2 genesis block + l2Allocs, err := foundry.LoadForgeAllocs(opts.l2AllocsPath) + if err != nil { + return err + } + + l2Genesis, err := genesis.BuildL2Genesis(config, l2Allocs, l1StartBlock) + if err != nil { + return fmt.Errorf("error creating l2 genesis: %w", err) + } + + // Write changes to state to actual state database + cel2Header, err := applyStateMigrationChanges(config, l2Genesis, newDBPath, opts.migrationBlockTime) + if err != nil { + return err + } + log.Info("Updated Cel2 state") + + rollupConfig, err := config.RollupConfig(l1StartBlock, cel2Header.Hash(), cel2Header.Number.Uint64()) + if err != nil { + return err + } + if err := rollupConfig.Check(); err != nil { + return fmt.Errorf("generated rollup config does not pass validation: %w", err) + } + + log.Info("Writing rollup config", "file", opts.outfileRollupConfig) + if err := jsonutil.WriteJSON(opts.outfileRollupConfig, rollupConfig, OutFilePerm); err != nil { + return err + } + + log.Info("State Migration Completed") + + return nil +} + +func timer(name string) func() { + start := time.Now() + return func() { + log.Info("TIMER", "process", name, "duration", time.Since(start)) + } +}
diff --git OP/op-chain-ops/cmd/celo-migrate/non-ancients.go CELO/op-chain-ops/cmd/celo-migrate/non-ancients.go new file mode 100644 index 0000000000000000000000000000000000000000..a9bee354f4dc31bbc932518f6fb93015ffba9ea7 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +func copyDbExceptAncients(oldDbPath, newDbPath string) error { + defer timer("copyDbExceptAncients")() + + log.Info("Copying files from old database (excluding ancients)", "process", "non-ancients") + + // Get rsync help output + cmdHelp := exec.Command("rsync", "--help") + output, _ := cmdHelp.CombinedOutput() + + // Convert output to string + outputStr := string(output) + + // Check for supported options + var cmd *exec.Cmd + // Prefer --info=progress2 over --progress + if strings.Contains(outputStr, "--info") { + cmd = exec.Command("rsync", "-v", "-a", "--info=progress2", "--exclude=ancient", "--delete", oldDbPath+"/", newDbPath) + } else if strings.Contains(outputStr, "--progress") { + cmd = exec.Command("rsync", "-v", "-a", "--progress", "--exclude=ancient", "--delete", oldDbPath+"/", newDbPath) + } else { + cmd = exec.Command("rsync", "-v", "-a", "--exclude=ancient", "--delete", oldDbPath+"/", newDbPath) + } + + // rsync copies any file with a different timestamp or size. + // + // '--exclude=ancient' excludes the ancient directory from the copy + // + // '--delete' Tells rsync to delete extraneous files from the receiving side (ones that aren’t on the sending side) + // + // '-a' archive mode; equals -rlptgoD. It is a quick way of saying you want recursion and want to preserve almost everything, including timestamps, ownerships, permissions, etc. + // Timestamps are important here because they are used to determine which files are newer and should be copied over. + // + // '--whole-file' This is the default when both the source and destination are specified as local paths, which they are here (oldDbPath and newDbPath). + // This option disables rsync’s delta-transfer algorithm, which causes all transferred files to be sent whole. The delta-transfer algorithm is normally used when the destination is a remote system. + + log.Info("Running rsync command", "command", cmd.String()) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to copy old database to new database: %w", err) + } + return nil +} + +func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSize uint64) (uint64, error) { + defer timer("migrateNonAncientsDb")() + + for i := numAncients; i <= lastBlock; i += batchSize { + numbersHash := rawdb.ReadAllHashesInRange(newDB, i, i+batchSize-1) + + log.Info("Processing Block Range", "process", "non-ancients", "from", i, "to(inclusve)", i+batchSize-1, "count", len(numbersHash)) + for _, numberHash := range numbersHash { + // read header and body + header := rawdb.ReadHeaderRLP(newDB, numberHash.Hash, numberHash.Number) + body := rawdb.ReadBodyRLP(newDB, numberHash.Hash, numberHash.Number) + + // transform header and body + newHeader, err := transformHeader(header) + if err != nil { + return 0, fmt.Errorf("failed to transform header: block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + } + newBody, err := transformBlockBody(body) + if err != nil { + return 0, fmt.Errorf("failed to transform body: block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + } + + if yes, newHash := hasSameHash(newHeader, numberHash.Hash[:]); !yes { + log.Error("Hash mismatch", "block", numberHash.Number, "oldHash", numberHash.Hash, "newHash", newHash) + return 0, fmt.Errorf("hash mismatch at block %d - %x", numberHash.Number, numberHash.Hash) + } + + // write header and body + batch := newDB.NewBatch() + rawdb.WriteBodyRLP(batch, numberHash.Hash, numberHash.Number, newBody) + _ = batch.Put(headerKey(numberHash.Number, numberHash.Hash), newHeader) + if err := batch.Write(); err != nil { + return 0, fmt.Errorf("failed to write header and body: block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + } + } + } + + migratedCount := lastBlock - numAncients + 1 + return migratedCount, nil +}
diff --git OP/op-chain-ops/cmd/celo-migrate/state.go CELO/op-chain-ops/cmd/celo-migrate/state.go new file mode 100644 index 0000000000000000000000000000000000000000..5dedb44a588553948efbe788581d150a70bbe545 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/state.go @@ -0,0 +1,340 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "os" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/addresses" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + + "github.com/holiman/uint256" +) + +var ( + Big10 = uint256.NewInt(10) + Big9 = uint256.NewInt(9) + Big18 = uint256.NewInt(18) + + OutFilePerm = os.FileMode(0o440) + + alfajoresChainId uint64 = 44787 + mainnetChainId uint64 = 42220 + + // Allowlist of accounts that are allowed to be overwritten + // If the value for an account is set to true, the nonce and storage will be overwritten + // This must be checked for each account, as this might create issues with contracts + // calling `CREATE` or `CREATE2` + accountOverwriteAllowlist = map[uint64]map[common.Address]bool{ + // Add any addresses that should be allowed to overwrite existing accounts here. + alfajoresChainId: { + // Create2Deployer + // common.HexToAddress("0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2"): false, + }, + } + distributionScheduleAddressMap = map[uint64]common.Address{ + alfajoresChainId: common.HexToAddress("0x78af211ad79bce6bf636640ce8c2c2b29e02365a"), + } + celoTokenAddressMap = map[uint64]common.Address{ + alfajoresChainId: addresses.CeloTokenAlfajoresAddress, + mainnetChainId: addresses.CeloTokenAddress, + } +) + +func applyStateMigrationChanges(config *genesis.DeployConfig, genesis *core.Genesis, dbPath string, migrationBlockTime uint64) (*types.Header, error) { + log.Info("Opening Celo database", "dbPath", dbPath) + + ldb, err := openDBWithoutFreezer(dbPath, false) + if err != nil { + return nil, fmt.Errorf("cannot open DB: %w", err) + } + log.Info("Loaded Celo L1 DB", "db", ldb) + + // Grab the hash of the tip of the legacy chain. + hash := rawdb.ReadHeadHeaderHash(ldb) + log.Info("Reading chain tip from database", "hash", hash) + + // Grab the header number. + num := rawdb.ReadHeaderNumber(ldb, hash) + if num == nil { + return nil, fmt.Errorf("cannot find header number for %s", hash) + } + log.Info("Reading chain tip num from database", "number", num) + + // Grab the full header. + header := rawdb.ReadHeader(ldb, hash, *num) + log.Info("Read header from database", "header", header) + + // We need to update the chain config to set the correct hardforks. + genesisHash := rawdb.ReadCanonicalHash(ldb, 0) + cfg := rawdb.ReadChainConfig(ldb, genesisHash) + if cfg == nil { + log.Crit("chain config not found") + } + log.Info("Read chain config from database", "config", cfg) + + // Set up the backing store. + underlyingDB := state.NewDatabase(ldb) + + // Open up the state database. + db, err := state.New(header.Root, underlyingDB, nil) + if err != nil { + return nil, fmt.Errorf("cannot open StateDB: %w", err) + } + + // Apply the changes to the state DB. + err = applyAllocsToState(db, genesis, accountOverwriteAllowlist[cfg.ChainID.Uint64()]) + if err != nil { + return nil, fmt.Errorf("cannot apply allocations to state: %w", err) + } + + // Initialize the distribution schedule contract + // This uses the original config which won't enable recent hardforks (and things like the PUSH0 opcode) + // This is fine, as the token uses solc 0.5.x and therefore compatible bytecode + err = setupDistributionSchedule(db, cfg) + if err != nil { + // An error here shouldn't stop the migration, just log it + log.Warn("Error setting up distribution schedule", "error", err) + } + + migrationBlock := new(big.Int).Add(header.Number, common.Big1) + + // We're done messing around with the database, so we can now commit the changes to the DB. + // Note that this doesn't actually write the changes to disk. + log.Info("Committing state DB") + newRoot, err := db.Commit(migrationBlock.Uint64(), true) + if err != nil { + return nil, err + } + + baseFee := new(big.Int).SetUint64(params.InitialBaseFee) + if header.BaseFee != nil { + baseFee = header.BaseFee + } + + if migrationBlockTime == 0 { + migrationBlockTime = uint64(time.Now().Unix()) + } + + // If gas limit was zero at the transition point use a default of 30M. + // Note that in op-geth we use gasLimit==0 to indicate a pre-gingerbread + // block and adjust encoding appropriately, so we must make sure that + // gasLimit is non-zero, bacause L2 blocks are all post gingerbread. + gasLimit := header.GasLimit + if gasLimit == 0 { + gasLimit = 30e6 + } + // Create the header for the Cel2 transition block. + cel2Header := &types.Header{ + ParentHash: header.Hash(), + UncleHash: types.EmptyUncleHash, + Coinbase: predeploys.SequencerFeeVaultAddr, + Root: newRoot, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + Bloom: types.Bloom{}, + Difficulty: new(big.Int).Set(common.Big0), + Number: migrationBlock, + GasLimit: gasLimit, + GasUsed: 0, + Time: migrationBlockTime, + Extra: []byte("CeL2 migration"), + MixDigest: common.Hash{}, + Nonce: types.BlockNonce{}, + BaseFee: baseFee, + WithdrawalsHash: &types.EmptyWithdrawalsHash, + BlobGasUsed: new(uint64), + ExcessBlobGas: new(uint64), + ParentBeaconRoot: &common.Hash{}, + } + log.Info("Build Cel2 migration header", "header", cel2Header) + + // Create the Cel2 transition block from the header. Note that there are no transactions, + // uncle blocks, or receipts in the Cel2 transition block. + cel2Block := types.NewBlock(cel2Header, nil, nil, nil, trie.NewStackTrie(nil)) + + // We did it! + log.Info( + "Built Cel2 migration block", + "hash", cel2Block.Hash(), + "root", cel2Block.Root(), + "number", cel2Block.NumberU64(), + ) + + log.Info("Committing trie DB") + if err := db.Database().TrieDB().Commit(newRoot, true); err != nil { + return nil, err + } + + // Next we write the Cel2 genesis block to the database. + rawdb.WriteTd(ldb, cel2Block.Hash(), cel2Block.NumberU64(), cel2Block.Difficulty()) + rawdb.WriteBlock(ldb, cel2Block) + rawdb.WriteReceipts(ldb, cel2Block.Hash(), cel2Block.NumberU64(), nil) + rawdb.WriteCanonicalHash(ldb, cel2Block.Hash(), cel2Block.NumberU64()) + rawdb.WriteHeadBlockHash(ldb, cel2Block.Hash()) + rawdb.WriteHeadFastBlockHash(ldb, cel2Block.Hash()) + rawdb.WriteHeadHeaderHash(ldb, cel2Block.Hash()) + + // Mark the first CeL2 block as finalized + rawdb.WriteFinalizedBlockHash(ldb, cel2Block.Hash()) + + // Set the standard options. + cfg.LondonBlock = cel2Block.Number() + cfg.BerlinBlock = cel2Block.Number() + cfg.ArrowGlacierBlock = cel2Block.Number() + cfg.GrayGlacierBlock = cel2Block.Number() + cfg.MergeNetsplitBlock = cel2Block.Number() + cfg.TerminalTotalDifficulty = big.NewInt(0) + cfg.TerminalTotalDifficultyPassed = true + cfg.ShanghaiTime = &cel2Header.Time + cfg.CancunTime = &cel2Header.Time + + // Set the Optimism options. + cfg.BedrockBlock = cel2Block.Number() + // Enable Regolith from the start of Bedrock + cfg.RegolithTime = new(uint64) // what are those? do we need those? + cfg.Optimism = &params.OptimismConfig{ + EIP1559Denominator: config.EIP1559Denominator, + EIP1559DenominatorCanyon: config.EIP1559DenominatorCanyon, + EIP1559Elasticity: config.EIP1559Elasticity, + } + cfg.CanyonTime = &cel2Header.Time + cfg.EcotoneTime = &cel2Header.Time + cfg.FjordTime = &cel2Header.Time + cfg.Cel2Time = &cel2Header.Time + + // Write the chain config to disk. + rawdb.WriteChainConfig(ldb, genesisHash, cfg) + marhslledConfig, err := json.Marshal(cfg) + if err != nil { + return nil, fmt.Errorf("failed to marshal chain config to JSON: %w", err) + } + log.Info("Wrote updated chain config", "config", string(marhslledConfig)) + + // We're done! + log.Info( + "Wrote CeL2 migration block", + "height", cel2Header.Number, + "root", cel2Header.Root.String(), + "hash", cel2Header.Hash().String(), + "timestamp", cel2Header.Time, + ) + + // Close the database handle + if err := ldb.Close(); err != nil { + return nil, err + } + + return cel2Header, nil +} + +// applyAllocsToState applies the account allocations from the allocation file to the state database. +// It creates new accounts, sets their nonce, balance, code, and storage values. +// If an account already exists, it adds the balance of the new account to the existing balance. +// If the code of an existing account is different from the code in the genesis block, it logs a warning. +// This changes the state root, so `Commit` needs to be called after this function. +func applyAllocsToState(db vm.StateDB, genesis *core.Genesis, allowlist map[common.Address]bool) error { + log.Info("Starting to migrate OP contracts into state DB") + + copyCounter := 0 + overwriteCounter := 0 + + for k, v := range genesis.Alloc { + // Check that the balance of the account to written is zero, + // as we must not create new CELO tokens + if v.Balance != nil && v.Balance.Cmp(big.NewInt(0)) != 0 { + return fmt.Errorf("account balance is not zero, would change celo supply: %s", k.Hex()) + } + + overwrite := true + if db.Exist(k) { + var allowed bool + overwrite, allowed = allowlist[k] + + // If the account is not allowed and has a non zero nonce or code size, bail out we will need to manually investigate how to handle this. + if !allowed && (db.GetCodeSize(k) > 0 || db.GetNonce(k) > 0) { + return fmt.Errorf("account exists and is not allowed, account: %s, nonce: %d, code: %d", k.Hex(), db.GetNonce(k), db.GetCode(k)) + } + + // This means that the account just has balance, in that case we wan to copy over the account + if db.GetCodeSize(k) == 0 && db.GetNonce(k) == 0 { + overwrite = true + } + } + + // This carries over any existing balance + db.CreateAccount(k) + + if overwrite { + overwriteCounter++ + + db.SetCode(k, v.Code) + db.SetNonce(k, v.Nonce) + for key, value := range v.Storage { + db.SetState(k, key, value) + } + } + + copyCounter++ + log.Info("Copied account", "address", k.Hex()) + } + + log.Info("Migrated OP contracts into state DB", "totalAllocs", len(genesis.Alloc), "copiedAccounts", copyCounter, "overwrittenAccounts", overwriteCounter) + return nil +} + +// setupDistributionSchedule sets up the distribution schedule contract with the correct balance +// The balance is set to the difference between the ceiling and the total supply of the token +func setupDistributionSchedule(db *state.StateDB, config *params.ChainConfig) error { + log.Info("Setting up CeloDistributionSchedule balance") + + celoDistributionScheduleAddress, exists := distributionScheduleAddressMap[config.ChainID.Uint64()] + if !exists { + return errors.New("DistributionSchedule address not configured for this chain, skipping migration step") + } + + if !db.Exist(celoDistributionScheduleAddress) { + return errors.New("DistributionSchedule account does not exist, skipping migration step") + } + + tokenAddress, exists := celoTokenAddressMap[config.ChainID.Uint64()] + if !exists { + return errors.New("celo token address not configured for this chain, skipping migration step") + } + log.Info("Read contract addresses", "tokenAddress", tokenAddress, "distributionScheduleAddress", celoDistributionScheduleAddress) + + // totalSupply is stored in the third slot + totalSupply := db.GetState(tokenAddress, common.HexToHash("0x02")).Big() + + // Get total supply of celo token + billion := new(uint256.Int).Exp(Big10, Big9) + ethInWei := new(uint256.Int).Exp(Big10, Big18) + + ceiling := new(uint256.Int).Mul(billion, ethInWei) + + supplyU256 := uint256.MustFromBig(totalSupply) + if supplyU256.Cmp(ceiling) > 0 { + return fmt.Errorf("supply %s is greater than ceiling %s", totalSupply, ceiling) + } + + balance := new(uint256.Int).Sub(ceiling, supplyU256) + // Don't discard existing balance of the account + balance = new(uint256.Int).Add(balance, db.GetBalance(celoDistributionScheduleAddress)) + db.SetBalance(celoDistributionScheduleAddress, balance) + + log.Info("Set up CeloDistributionSchedule balance", "distributionScheduleAddress", celoDistributionScheduleAddress, "balance", balance, "total_supply", supplyU256, "ceiling", ceiling) + return nil +}
diff --git OP/op-chain-ops/cmd/celo-migrate/state_test.go CELO/op-chain-ops/cmd/celo-migrate/state_test.go new file mode 100644 index 0000000000000000000000000000000000000000..34417c2e9a9186dccfd71ee93144ccc139b83bbc --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/state_test.go @@ -0,0 +1,151 @@ +package main + +import ( + "bytes" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" +) + +var ( + contractCode = []byte{0x01, 0x02} + defaultBalance int64 = 123 +) + +func TestApplyAllocsToState(t *testing.T) { + tests := []struct { + name string + addr common.Address + existingAccount *types.Account + newAccount types.Account + allowlist map[common.Address]bool + balanceInAccount bool + wantErr bool + }{ + { + name: "Write to empty account", + addr: common.HexToAddress("01"), + newAccount: types.Account{ + Code: contractCode, + Nonce: 1, + }, + balanceInAccount: false, + wantErr: false, + }, + { + name: "Copy account with non-zero balance fails", + addr: common.HexToAddress("a"), + existingAccount: &types.Account{ + Balance: big.NewInt(defaultBalance), + }, + newAccount: types.Account{ + Balance: big.NewInt(1), + }, + wantErr: true, + }, + { + name: "Write to account with only balance should overwrite and keep balance", + addr: common.HexToAddress("a"), + existingAccount: &types.Account{ + Balance: big.NewInt(defaultBalance), + }, + newAccount: types.Account{ + Code: contractCode, + Nonce: 5, + }, + balanceInAccount: true, + wantErr: false, + }, + { + name: "Write to account with existing nonce fails", + addr: common.HexToAddress("c"), + existingAccount: &types.Account{ + Balance: big.NewInt(defaultBalance), + Nonce: 5, + }, + newAccount: types.Account{ + Code: contractCode, + Nonce: 5, + }, + wantErr: true, + }, + { + name: "Write to account with contract code fails", + addr: common.HexToAddress("b"), + existingAccount: &types.Account{ + Balance: big.NewInt(defaultBalance), + Code: bytes.Repeat([]byte{0x01}, 10), + }, + newAccount: types.Account{ + Code: contractCode, + Nonce: 5, + }, + wantErr: true, + }, + { + name: "Write account with allowlist overwrites", + addr: common.HexToAddress("d"), + existingAccount: &types.Account{ + Balance: big.NewInt(defaultBalance), + Code: bytes.Repeat([]byte{0x01}, 10), + }, + newAccount: types.Account{ + Code: contractCode, + Nonce: 5, + }, + allowlist: map[common.Address]bool{common.HexToAddress("d"): true}, + balanceInAccount: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + tdb := state.NewDatabase(db) + sdb, _ := state.New(types.EmptyRootHash, tdb, nil) + + if tt.existingAccount != nil { + sdb.CreateAccount(tt.addr) + + if tt.existingAccount.Balance != nil { + sdb.SetBalance(tt.addr, uint256.MustFromBig(tt.existingAccount.Balance)) + } + if tt.existingAccount.Nonce != 0 { + sdb.SetNonce(tt.addr, tt.existingAccount.Nonce) + } + if tt.existingAccount.Code != nil { + sdb.SetCode(tt.addr, tt.existingAccount.Code) + } + } + + if err := applyAllocsToState(sdb, &core.Genesis{Alloc: types.GenesisAlloc{tt.addr: tt.newAccount}}, tt.allowlist); (err != nil) != tt.wantErr { + t.Errorf("applyAllocsToState() error = %v, wantErr %v", err, tt.wantErr) + } + + // Don't check account state if an error was thrown + if tt.wantErr { + return + } + + if !sdb.Exist(tt.addr) { + t.Errorf("account does not exists as expected: %v", tt.addr.Hex()) + } + + assert.Equal(t, tt.newAccount.Nonce, sdb.GetNonce(tt.addr)) + assert.Equal(t, tt.newAccount.Code, sdb.GetCode(tt.addr)) + + if tt.balanceInAccount { + assert.True(t, big.NewInt(defaultBalance).Cmp(sdb.GetBalance(tt.addr).ToBig()) == 0) + } else { + assert.True(t, big.NewInt(0).Cmp(sdb.GetBalance(tt.addr).ToBig()) == 0) + } + }) + } +}
diff --git OP/op-chain-ops/cmd/celo-migrate/transform.go CELO/op-chain-ops/cmd/celo-migrate/transform.go new file mode 100644 index 0000000000000000000000000000000000000000..5a80e8a51566f47408976d16f94885e4937108d3 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/transform.go @@ -0,0 +1,109 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + IstanbulExtraVanity = 32 // Fixed number of extra-data bytes reserved for validator vanity +) + +// IstanbulAggregatedSeal is the aggregated seal for Istanbul blocks +type IstanbulAggregatedSeal struct { + // Bitmap is a bitmap having an active bit for each validator that signed this block + Bitmap *big.Int + // Signature is an aggregated BLS signature resulting from signatures by each validator that signed this block + Signature []byte + // Round is the round in which the signature was created. + Round *big.Int +} + +// IstanbulExtra is the extra-data for Istanbul blocks +type IstanbulExtra struct { + // AddedValidators are the validators that have been added in the block + AddedValidators []common.Address + // AddedValidatorsPublicKeys are the BLS public keys for the validators added in the block + AddedValidatorsPublicKeys [][96]byte + // RemovedValidators is a bitmap having an active bit for each removed validator in the block + RemovedValidators *big.Int + // Seal is an ECDSA signature by the proposer + Seal []byte + // AggregatedSeal contains the aggregated BLS signature created via IBFT consensus. + AggregatedSeal IstanbulAggregatedSeal + // ParentAggregatedSeal contains and aggregated BLS signature for the previous block. + ParentAggregatedSeal IstanbulAggregatedSeal +} + +// transformHeader removes the aggregated seal from the header +func transformHeader(header []byte) ([]byte, error) { + newHeader := new(types.Header) + err := rlp.DecodeBytes(header, &newHeader) + if err != nil { + return nil, err + } + + if len(newHeader.Extra) < IstanbulExtraVanity { + return nil, errors.New("invalid istanbul header extra-data") + } + + istanbulExtra := IstanbulExtra{} + err = rlp.DecodeBytes(newHeader.Extra[IstanbulExtraVanity:], &istanbulExtra) + if err != nil { + return nil, err + } + + istanbulExtra.AggregatedSeal = IstanbulAggregatedSeal{} + + payload, err := rlp.EncodeToBytes(&istanbulExtra) + if err != nil { + return nil, err + } + + newHeader.Extra = append(newHeader.Extra[:IstanbulExtraVanity], payload...) + + return rlp.EncodeToBytes(newHeader) +} + +func hasSameHash(newHeader, oldHash []byte) (bool, common.Hash) { + newHash := crypto.Keccak256Hash(newHeader) + return bytes.Equal(oldHash, newHash.Bytes()), newHash +} + +// transformBlockBody migrates the block body from the old format to the new format (works with []byte input output) +func transformBlockBody(oldBodyData []byte) ([]byte, error) { + // decode body into celo-blockchain Body structure + // remove epochSnarkData and randomness data + var celoBody struct { + Transactions types.Transactions + Randomness rlp.RawValue + EpochSnarkData rlp.RawValue + } + if err := rlp.DecodeBytes(oldBodyData, &celoBody); err != nil { + // body may have already been transformed in a previous migration + body := types.Body{} + if err := rlp.DecodeBytes(oldBodyData, &body); err == nil { + return oldBodyData, nil + } + return nil, fmt.Errorf("failed to RLP decode body: %w", err) + } + + // transform into op-geth types.Body structure + newBody := types.Body{ + Transactions: celoBody.Transactions, + Uncles: []*types.Header{}, + } + newBodyData, err := rlp.EncodeToBytes(newBody) + if err != nil { + return nil, fmt.Errorf("failed to RLP encode body: %w", err) + } + + return newBodyData, nil +}
diff --git OP/op-chain-ops/cmd/check-derivation/main.go CELO/op-chain-ops/cmd/check-derivation/main.go index 499b128f8767fe560d92ee66f3ed770c373d7bf4..88a13f6efce7ffb37d36b6b709c0e698495793b7 100644 --- OP/op-chain-ops/cmd/check-derivation/main.go +++ CELO/op-chain-ops/cmd/check-derivation/main.go @@ -225,7 +225,7 @@ data := testutils.RandomData(rng, 10) var txData types.TxData switch txType { case types.LegacyTxType: - gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } @@ -242,7 +242,7 @@ accessList := types.AccessList{types.AccessTuple{ Address: randomAddress, StorageKeys: []common.Hash{common.HexToHash("0x1234")}, }} - gasLimit, err := core.IntrinsicGas(data, accessList, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, accessList, false, true, true, false, nil) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } @@ -257,7 +257,7 @@ AccessList: accessList, Data: data, } case types.DynamicFeeTxType: - gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) }
diff --git OP/op-chain-ops/cmd/op-version-check/README.md CELO/op-chain-ops/cmd/op-version-check/README.md deleted file mode 100644 index 29e5f08ab4de4f273213a2561374d844a0d191be..0000000000000000000000000000000000000000 --- OP/op-chain-ops/cmd/op-version-check/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# op-version-check - -A CLI tool for determining which contract versions are deployed for -chains in a superchain. It will output a JSON file that contains a -list of each chain's versions. It is assumed that the implementations -that are being checked have already been deployed and their contract -addresses exist inside of the `superchain-registry` repository. It is -also assumed that the semantic version file in the `superchain-registry` -has been updated. The tool will output the semantic versioning to -determine which contract versions are deployed. - -### Configuration - -#### L1 RPC URL - -The L1 RPC URL is used to determine which superchain to target. All -L2s that are not based on top of the L1 chain that corresponds to the -L1 RPC URL are filtered out from being checked. It also is used to -double check that the data in the `superchain-registry` is correct. - -#### Chain IDs - -A list of L2 chain IDs can be passed that will be used to filter which -L2 chains will have their versions checked. Omitting this argument will -result in all chains in the superchain being considered. - -#### Deploy Config - -The path to the `deploy-config` directory in the contracts package. -Since multiple L2 networks may be considered in the check, the `deploy-config` -directory must be passed and then the particular deploy config files will -be read out of the directory as needed. - -#### Outfile - -The file that the versions should be written to. If omitted, the file -will be written to stdout - -#### Usage - -It can be built and run using the [Makefile](../../Makefile) `op-version-check` -target. Run `make op-version-check` to create a binary in [../../bin/op-version-check](../../bin/op-version-check) -that can be executed, optionally providing the `--l1-rpc-url`, `--chain-ids`, -`--superchain-target`, and `--outfile` flags. - -```sh -./bin/op-version-check -```
diff --git OP/op-chain-ops/cmd/op-version-check/main.go CELO/op-chain-ops/cmd/op-version-check/main.go deleted file mode 100644 index 163ff8599ef56a63a0640924fb18a005c2abb372..0000000000000000000000000000000000000000 --- OP/op-chain-ops/cmd/op-version-check/main.go +++ /dev/null @@ -1,169 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "os" - - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/mattn/go-isatty" - "github.com/urfave/cli/v2" - "golang.org/x/exp/maps" - - "github.com/ethereum-optimism/optimism/op-chain-ops/upgrades" - "github.com/ethereum-optimism/optimism/op-service/jsonutil" - oplog "github.com/ethereum-optimism/optimism/op-service/log" - - "github.com/ethereum-optimism/superchain-registry/superchain" -) - -type Contract struct { - Version string `yaml:"version"` - Address superchain.Address `yaml:"address"` -} - -type ChainVersionCheck struct { - Name string `yaml:"name"` - ChainID uint64 `yaml:"chain_id"` - Contracts map[string]Contract `yaml:"contracts"` -} - -func main() { - color := isatty.IsTerminal(os.Stderr.Fd()) - oplog.SetGlobalLogHandler(log.NewTerminalHandler(os.Stderr, color)) - - app := &cli.App{ - Name: "op-version-check", - Usage: "Determine which contract versions are deployed for chains in a superchain", - Flags: []cli.Flag{ - &cli.StringSliceFlag{ - Name: "l1-rpc-urls", - Usage: "L1 RPC URLs, the chain ID will be used to determine the superchain", - EnvVars: []string{"L1_RPC_URLS"}, - }, - &cli.StringSliceFlag{ - Name: "l2-rpc-urls", - Usage: "L2 RPC URLs, corresponding to chains to check versions for. Corresponds to all chains if empty", - EnvVars: []string{"L2_RPC_URLS"}, - }, - &cli.PathFlag{ - Name: "outfile", - Usage: "The file to write the output to. If not specified, output is written to stdout", - EnvVars: []string{"OUTFILE"}, - }, - }, - Action: entrypoint, - } - - if err := app.Run(os.Args); err != nil { - log.Crit("error op-version-check", "err", err) - } -} - -// entrypoint contains the main logic of the script -func entrypoint(ctx *cli.Context) error { - l1RPCURLs := ctx.StringSlice("l1-rpc-urls") - l2RPCURLs := ctx.StringSlice("l2-rpc-urls") - - var l2ChainIDs []uint64 - - // If no L2 RPC URLs are specified, we check all chains for the L1 RPC URL - if len(l2RPCURLs) == 0 { - l2ChainIDs = maps.Keys(superchain.OPChains) - } else { - for _, l2RPCURL := range l2RPCURLs { - client, err := ethclient.Dial(l2RPCURL) - if err != nil { - return errors.New("cannot create L2 client") - } - - l2ChainID, err := client.ChainID(ctx.Context) - if err != nil { - return fmt.Errorf("cannot fetch L2 chain ID: %w", err) - } - - l2ChainIDs = append(l2ChainIDs, l2ChainID.Uint64()) - } - } - - output := []ChainVersionCheck{} - - for _, l2ChainID := range l2ChainIDs { - chainConfig := superchain.OPChains[l2ChainID] - - if chainConfig.ChainID != l2ChainID { - return fmt.Errorf("mismatched chain IDs: %d != %d", chainConfig.ChainID, l2ChainID) - } - - for _, l1RPCURL := range l1RPCURLs { - client, err := ethclient.Dial(l1RPCURL) - if err != nil { - return errors.New("cannot create L1 client") - } - - l1ChainID, err := client.ChainID(ctx.Context) - if err != nil { - return fmt.Errorf("cannot fetch L1 chain ID: %w", err) - } - - sc, ok := superchain.Superchains[chainConfig.Superchain] - if !ok { - return fmt.Errorf("superchain name %s not registered", chainConfig.Superchain) - } - - declaredL1ChainID := sc.Config.L1.ChainID - - if l1ChainID.Uint64() != declaredL1ChainID { - // L2 corresponds to a different superchain than L1, skip - log.Info("Ignoring L1/L2", "l1-chain-id", l1ChainID, "l2-chain-id", l2ChainID) - continue - } - - log.Info(chainConfig.Name, "l1-chain-id", l1ChainID, "l2-chain-id", l2ChainID) - - log.Info("Detecting on chain contracts") - // Tracking the individual addresses can be deprecated once the system is upgraded - // to the new contracts where the system config has a reference to each address. - addresses, ok := superchain.Addresses[l2ChainID] - if !ok { - return fmt.Errorf("no addresses for chain ID %d", l2ChainID) - } - versions, err := upgrades.GetContractVersions(ctx.Context, addresses, chainConfig, client) - if err != nil { - return fmt.Errorf("error getting contract versions: %w", err) - } - - contracts := make(map[string]Contract) - - contracts["AddressManager"] = Contract{Version: "null", Address: addresses.AddressManager} - contracts["L1CrossDomainMessenger"] = Contract{Version: versions.L1CrossDomainMessenger, Address: addresses.L1CrossDomainMessengerProxy} - contracts["L1ERC721Bridge"] = Contract{Version: versions.L1ERC721Bridge, Address: addresses.L1ERC721BridgeProxy} - contracts["L1StandardBridge"] = Contract{Version: versions.L1ERC721Bridge, Address: addresses.L1StandardBridgeProxy} - contracts["L2OutputOracle"] = Contract{Version: versions.L2OutputOracle, Address: addresses.L2OutputOracleProxy} - contracts["OptimismMintableERC20Factory"] = Contract{Version: versions.OptimismMintableERC20Factory, Address: addresses.OptimismMintableERC20FactoryProxy} - contracts["OptimismPortal"] = Contract{Version: versions.OptimismPortal, Address: addresses.OptimismPortalProxy} - contracts["SystemConfig"] = Contract{Version: versions.SystemConfig, Address: addresses.SystemConfigProxy} - contracts["ProxyAdmin"] = Contract{Version: "null", Address: addresses.ProxyAdmin} - - output = append(output, ChainVersionCheck{Name: chainConfig.Name, ChainID: l2ChainID, Contracts: contracts}) - - log.Info("Successfully processed contract versions", "chain", chainConfig.Name, "l1-chain-id", l1ChainID, "l2-chain-id", l2ChainID) - break - } - } - // Write contract versions to disk or stdout - if outfile := ctx.Path("outfile"); outfile != "" { - if err := jsonutil.WriteJSON(outfile, output, 0o666); err != nil { - return err - } - } else { - data, err := json.MarshalIndent(output, "", " ") - if err != nil { - return err - } - fmt.Println(string(data)) - } - return nil -}
diff --git OP/op-chain-ops/foundry/artifact.go CELO/op-chain-ops/foundry/artifact.go index 93791ef806d08b68692c0ec9b76f07ac9eaec3ee..0e0dfcb463f9236ef86f35501b473ee9b1c942ad 100644 --- OP/op-chain-ops/foundry/artifact.go +++ CELO/op-chain-ops/foundry/artifact.go @@ -4,11 +4,17 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "strings"   + "github.com/holiman/uint256" + "golang.org/x/exp/maps" + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" )   // Artifact represents a foundry compilation artifact. @@ -49,7 +55,7 @@ } return json.Marshal(artifact) }   -// artifactMarshaling is a helper struct for marshaling and unmarshaling +// artifactMarshaling is a helper struct for marshaling and unmarshalling // foundry artifacts. type artifactMarshaling struct { ABI json.RawMessage `json:"abi"` @@ -66,7 +72,7 @@ LinkReferences json.RawMessage `json:"linkReferences"` ImmutableReferences json.RawMessage `json:"immutableReferences,omitempty"` }   -// DeployedBytecode represents the bytecode section of the solc compiler output. +// Bytecode represents the bytecode section of the solc compiler output. type Bytecode struct { SourceMap string `json:"sourceMap"` Object hexutil.Bytes `json:"object"` @@ -86,3 +92,53 @@ return nil, err } return &artifact, nil } + +type ForgeAllocs struct { + Accounts types.GenesisAlloc +} + +func (d *ForgeAllocs) Copy() *ForgeAllocs { + out := make(types.GenesisAlloc, len(d.Accounts)) + maps.Copy(out, d.Accounts) + return &ForgeAllocs{Accounts: out} +} + +func (d *ForgeAllocs) UnmarshalJSON(b []byte) error { + // forge, since integrating Alloy, likes to hex-encode everything. + type forgeAllocAccount struct { + Balance hexutil.U256 `json:"balance"` + Nonce hexutil.Uint64 `json:"nonce"` + Code hexutil.Bytes `json:"code,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + } + var allocs map[common.Address]forgeAllocAccount + if err := json.Unmarshal(b, &allocs); err != nil { + return err + } + d.Accounts = make(types.GenesisAlloc, len(allocs)) + for addr, acc := range allocs { + acc := acc + d.Accounts[addr] = types.Account{ + Code: acc.Code, + Storage: acc.Storage, + Balance: (*uint256.Int)(&acc.Balance).ToBig(), + Nonce: (uint64)(acc.Nonce), + PrivateKey: nil, + } + } + return nil +} + +func LoadForgeAllocs(allocsPath string) (*ForgeAllocs, error) { + path := filepath.Join(allocsPath) + f, err := os.OpenFile(path, os.O_RDONLY, 0644) + if err != nil { + return nil, fmt.Errorf("failed to open forge allocs %q: %w", path, err) + } + defer f.Close() + var out ForgeAllocs + if err := json.NewDecoder(f).Decode(&out); err != nil { + return nil, fmt.Errorf("failed to json-decode forge allocs %q: %w", path, err) + } + return &out, nil +}
diff --git OP/op-chain-ops/genesis/config.go CELO/op-chain-ops/genesis/config.go index a2661b057ecb0cbddb499ab29ba570a0ea264616..a7614f95bba46c543e7e5c435eb52e08ad5a32cf 100644 --- OP/op-chain-ops/genesis/config.go +++ CELO/op-chain-ops/genesis/config.go @@ -10,9 +10,6 @@ "os" "path/filepath" "reflect"   - "github.com/holiman/uint256" - "golang.org/x/exp/maps" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -292,6 +289,9 @@ L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"`   // UseInterop is a flag that indicates if the system is using interop UseInterop bool `json:"useInterop,omitempty"` + + // DeployCeloContracts indicates whether to deploy Celo contracts. + DeployCeloContracts bool `json:"deployCeloContracts"` }   // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy @@ -441,19 +441,21 @@ log.Warn("DisputeGameFinalityDelaySeconds is 0") } if d.UsePlasma { if d.DAChallengeWindow == 0 { - return fmt.Errorf("%w: DAChallengeWindow cannot be 0 when using plasma mode", ErrInvalidDeployConfig) + return fmt.Errorf("%w: DAChallengeWindow cannot be 0 when using alt-da mode", ErrInvalidDeployConfig) } if d.DAResolveWindow == 0 { - return fmt.Errorf("%w: DAResolveWindow cannot be 0 when using plasma mode", ErrInvalidDeployConfig) + return fmt.Errorf("%w: DAResolveWindow cannot be 0 when using alt-da mode", ErrInvalidDeployConfig) } if !(d.DACommitmentType == plasma.KeccakCommitmentString || d.DACommitmentType == plasma.GenericCommitmentString) { return fmt.Errorf("%w: DACommitmentType must be either KeccakCommtiment or GenericCommitment", ErrInvalidDeployConfig) } } if d.UseCustomGasToken { - if d.CustomGasTokenAddress == (common.Address{}) { - return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig) - } + // NOTE: we are using the address(0) as an instruction to deploy the L1 token, + // so this deploy-config validation has to be disabled + // if d.CustomGasTokenAddress == (common.Address{}) { + // return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig) + // } log.Info("Using custom gas token", "address", d.CustomGasTokenAddress) } // checkFork checks that fork A is before or at the same time as fork B @@ -520,9 +522,9 @@ if d.OptimismPortalProxy == (common.Address{}) { return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig) } if d.UsePlasma && d.DACommitmentType == plasma.KeccakCommitmentString && d.DAChallengeProxy == (common.Address{}) { - return fmt.Errorf("%w: DAChallengeContract cannot be address(0) when using plasma mode", ErrInvalidDeployConfig) + return fmt.Errorf("%w: DAChallengeContract cannot be address(0) when using alt-da mode", ErrInvalidDeployConfig) } else if d.UsePlasma && d.DACommitmentType == plasma.GenericCommitmentString && d.DAChallengeProxy != (common.Address{}) { - return fmt.Errorf("%w: DAChallengeContract must be address(0) when using generic commitments in plasma mode", ErrInvalidDeployConfig) + return fmt.Errorf("%w: DAChallengeContract must be address(0) when using generic commitments in alt-da mode", ErrInvalidDeployConfig) } return nil } @@ -660,6 +662,7 @@ EcotoneTime: d.EcotoneTime(l1StartBlock.Time()), FjordTime: d.FjordTime(l1StartBlock.Time()), InteropTime: d.InteropTime(l1StartBlock.Time()), PlasmaConfig: plasma, + Cel2Time: d.RegolithTime(l1StartBlock.Time()), }, nil }   @@ -794,42 +797,6 @@ return nil, fmt.Errorf("cannot unmarshal L1 deployments: %w", err) }   return &deployments, nil -} - -type ForgeAllocs struct { - Accounts types.GenesisAlloc -} - -func (d *ForgeAllocs) Copy() *ForgeAllocs { - out := make(types.GenesisAlloc, len(d.Accounts)) - maps.Copy(out, d.Accounts) - return &ForgeAllocs{Accounts: out} -} - -func (d *ForgeAllocs) UnmarshalJSON(b []byte) error { - // forge, since integrating Alloy, likes to hex-encode everything. - type forgeAllocAccount struct { - Balance hexutil.U256 `json:"balance"` - Nonce hexutil.Uint64 `json:"nonce"` - Code hexutil.Bytes `json:"code,omitempty"` - Storage map[common.Hash]common.Hash `json:"storage,omitempty"` - } - var allocs map[common.Address]forgeAllocAccount - if err := json.Unmarshal(b, &allocs); err != nil { - return err - } - d.Accounts = make(types.GenesisAlloc, len(allocs)) - for addr, acc := range allocs { - acc := acc - d.Accounts[addr] = types.Account{ - Code: acc.Code, - Storage: acc.Storage, - Balance: (*uint256.Int)(&acc.Balance).ToBig(), - Nonce: (uint64)(acc.Nonce), - PrivateKey: nil, - } - } - return nil }   type MarshalableRPCBlockNumberOrHash rpc.BlockNumberOrHash
diff --git OP/op-chain-ops/genesis/genesis.go CELO/op-chain-ops/genesis/genesis.go index bbfb15c346496a1f19cccd5915f4e457bd3f8a2b..93953a06089cda171548e38d9538ee6ead06a2b1 100644 --- OP/op-chain-ops/genesis/genesis.go +++ CELO/op-chain-ops/genesis/genesis.go @@ -68,6 +68,7 @@ CancunTime: config.EcotoneTime(block.Time()), EcotoneTime: config.EcotoneTime(block.Time()), FjordTime: config.FjordTime(block.Time()), InteropTime: config.InteropTime(block.Time()), + Cel2Time: config.RegolithTime(block.Time()), Optimism: &params.OptimismConfig{ EIP1559Denominator: eip1559Denom, EIP1559Elasticity: eip1559Elasticity,
diff --git OP/op-chain-ops/genesis/layer_one.go CELO/op-chain-ops/genesis/layer_one.go index ee9d408212a9ddde0dba96bba9bfffdcb29cbafc..16f1a3c48c2dface5b8742452a16d8e2f3c7ec24 100644 --- OP/op-chain-ops/genesis/layer_one.go +++ CELO/op-chain-ops/genesis/layer_one.go @@ -4,6 +4,8 @@ import ( "fmt" "math/big"   + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/log" @@ -18,7 +20,7 @@ // BuildL1DeveloperGenesis will create a L1 genesis block after creating // all of the state required for an Optimism network to function. // It is expected that the dump contains all of the required state to bootstrap // the L1 chain. -func BuildL1DeveloperGenesis(config *DeployConfig, dump *ForgeAllocs, l1Deployments *L1Deployments) (*core.Genesis, error) { +func BuildL1DeveloperGenesis(config *DeployConfig, dump *foundry.ForgeAllocs, l1Deployments *L1Deployments) (*core.Genesis, error) { log.Info("Building developer L1 genesis block") genesis, err := NewL1Genesis(config) if err != nil {
diff --git OP/op-chain-ops/genesis/layer_two.go CELO/op-chain-ops/genesis/layer_two.go index 6cd0bbe7d27f59be70721d6b148eb9347d1eaed6..a898afc70aaa5557d270910bcf66b0bb09e33a2b 100644 --- OP/op-chain-ops/genesis/layer_two.go +++ CELO/op-chain-ops/genesis/layer_two.go @@ -1,11 +1,8 @@ package genesis   import ( - "encoding/json" "fmt" "math/big" - "os" - "path/filepath"   hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet" "github.com/holiman/uint256" @@ -16,6 +13,7 @@ "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto"   + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-service/predeploys" )   @@ -34,10 +32,10 @@ // mnemonic for the test accounts in hardhat/foundry testMnemonic = "test test test test test test test test test test test junk" )   -type AllocsLoader func(mode L2AllocsMode) *ForgeAllocs +type AllocsLoader func(mode L2AllocsMode) *foundry.ForgeAllocs   // BuildL2Genesis will build the L2 genesis block. -func BuildL2Genesis(config *DeployConfig, dump *ForgeAllocs, l1StartBlock *types.Block) (*core.Genesis, error) { +func BuildL2Genesis(config *DeployConfig, dump *foundry.ForgeAllocs, l1StartBlock *types.Block) (*core.Genesis, error) { genspec, err := NewL2Genesis(config, l1StartBlock) if err != nil { return nil, err @@ -94,17 +92,3 @@ } } return false, nil } - -func LoadForgeAllocs(allocsPath string) (*ForgeAllocs, error) { - path := filepath.Join(allocsPath) - f, err := os.OpenFile(path, os.O_RDONLY, 0644) - if err != nil { - return nil, fmt.Errorf("failed to open forge allocs %q: %w", path, err) - } - defer f.Close() - var out ForgeAllocs - if err := json.NewDecoder(f).Decode(&out); err != nil { - return nil, fmt.Errorf("failed to json-decode forge allocs %q: %w", path, err) - } - return &out, nil -}
diff --git OP/op-chain-ops/genesis/testdata/test-deploy-config-full.json CELO/op-chain-ops/genesis/testdata/test-deploy-config-full.json index c0aefac625ff5b0a11560dd40ee2ed22c53a7416..09415e40bfd164a02482b916eb2ac2ca8be04479 100644 --- OP/op-chain-ops/genesis/testdata/test-deploy-config-full.json +++ CELO/op-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -92,5 +92,6 @@ "daCommitmentType": "KeccakCommtiment", "daChallengeProxy": "0x0000000000000000000000000000000000000000", "daChallengeWindow": 0, "daResolveWindow": 0, - "daResolverRefundPercentage": 0 + "daResolverRefundPercentage": 0, + "deployCeloContracts": false }
diff --git OP/op-chain-ops/justfile CELO/op-chain-ops/justfile index 9775f535d227d4d2dacfa690fe318b2f1b78f23f..b0ec8ee270a586b46ea9383d29bf5041e46273bd 100644 --- OP/op-chain-ops/justfile +++ CELO/op-chain-ops/justfile @@ -23,3 +23,18 @@ build_abi SystemConfig #build_abi ISemver build_abi ProxyAdmin build_abi StorageSetter + +bindings-celo-migrate: + #!/usr/bin/env bash + set -euxo pipefail + + build_abi() { + local lowercase=$(echo "$2" | awk '{print tolower($0)}') + abigen \ + --abi "{{abis}}/$1.json" \ + --pkg bindings \ + --out "cmd/celo-migrate/bindings/$lowercase.go" \ + --type $2 + } + + build_abi GoldToken CeloToken
diff --git OP/op-challenger/cmd/main_test.go CELO/op-challenger/cmd/main_test.go index 7e77f19408e8b5832cd152c1443fcfcb367d3c94..e6234b063b3400b5954271cc3ae5018769d6558b 100644 --- OP/op-challenger/cmd/main_test.go +++ CELO/op-challenger/cmd/main_test.go @@ -277,7 +277,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-bin", "--asterisc-bin=./asterisc")) - require.Equal(t, "./asterisc", cfg.AsteriscBin) + require.Equal(t, "./asterisc", cfg.Asterisc.VmBin) }) })   @@ -292,7 +292,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-server", "--asterisc-server=./op-program")) - require.Equal(t, "./op-program", cfg.AsteriscServer) + require.Equal(t, "./op-program", cfg.Asterisc.Server) }) })   @@ -349,12 +349,12 @@ t.Run(fmt.Sprintf("TestAsteriscSnapshotFreq-%v", traceType), func(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(traceType)) - require.Equal(t, config.DefaultAsteriscSnapshotFreq, cfg.AsteriscSnapshotFreq) + require.Equal(t, config.DefaultAsteriscSnapshotFreq, cfg.Asterisc.SnapshotFreq) })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(traceType, "--asterisc-snapshot-freq=1234")) - require.Equal(t, uint(1234), cfg.AsteriscSnapshotFreq) + require.Equal(t, uint(1234), cfg.Asterisc.SnapshotFreq) })   t.Run("Invalid", func(t *testing.T) { @@ -366,12 +366,12 @@ t.Run(fmt.Sprintf("TestAsteriscInfoFreq-%v", traceType), func(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(traceType)) - require.Equal(t, config.DefaultAsteriscInfoFreq, cfg.AsteriscInfoFreq) + require.Equal(t, config.DefaultAsteriscInfoFreq, cfg.Asterisc.InfoFreq) })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(traceType, "--asterisc-info-freq=1234")) - require.Equal(t, uint(1234), cfg.AsteriscInfoFreq) + require.Equal(t, uint(1234), cfg.Asterisc.InfoFreq) })   t.Run("Invalid", func(t *testing.T) { @@ -432,7 +432,7 @@ delete(args, "--asterisc-network") delete(args, "--game-factory-address") args["--network"] = "op-sepolia" cfg := configForArgs(t, toArgList(args)) - require.Equal(t, "op-sepolia", cfg.AsteriscNetwork) + require.Equal(t, "op-sepolia", cfg.Asterisc.Network) })   t.Run("MustNotSpecifyNetworkAndAsteriscNetwork", func(t *testing.T) { @@ -442,7 +442,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-network", "--asterisc-network", testNetwork)) - require.Equal(t, testNetwork, cfg.AsteriscNetwork) + require.Equal(t, testNetwork, cfg.Asterisc.Network) }) })   @@ -453,7 +453,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-network", "--asterisc-rollup-config=rollup.json", "--asterisc-l2-genesis=genesis.json")) - require.Equal(t, "rollup.json", cfg.AsteriscRollupConfigPath) + require.Equal(t, "rollup.json", cfg.Asterisc.RollupConfigPath) }) })   @@ -464,7 +464,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-network", "--asterisc-rollup-config=rollup.json", "--asterisc-l2-genesis=genesis.json")) - require.Equal(t, "genesis.json", cfg.AsteriscL2GenesisPath) + require.Equal(t, "genesis.json", cfg.Asterisc.L2GenesisPath) }) }) } @@ -502,7 +502,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-bin", "--cannon-bin=./cannon")) - require.Equal(t, "./cannon", cfg.CannonBin) + require.Equal(t, "./cannon", cfg.Cannon.VmBin) }) })   @@ -517,7 +517,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-server", "--cannon-server=./op-program")) - require.Equal(t, "./op-program", cfg.CannonServer) + require.Equal(t, "./op-program", cfg.Cannon.Server) }) })   @@ -570,12 +570,12 @@ t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", traceType), func(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(traceType)) - require.Equal(t, config.DefaultCannonSnapshotFreq, cfg.CannonSnapshotFreq) + require.Equal(t, config.DefaultCannonSnapshotFreq, cfg.Cannon.SnapshotFreq) })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(traceType, "--cannon-snapshot-freq=1234")) - require.Equal(t, uint(1234), cfg.CannonSnapshotFreq) + require.Equal(t, uint(1234), cfg.Cannon.SnapshotFreq) })   t.Run("Invalid", func(t *testing.T) { @@ -587,12 +587,12 @@ t.Run(fmt.Sprintf("TestCannonInfoFreq-%v", traceType), func(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(traceType)) - require.Equal(t, config.DefaultCannonInfoFreq, cfg.CannonInfoFreq) + require.Equal(t, config.DefaultCannonInfoFreq, cfg.Cannon.InfoFreq) })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(traceType, "--cannon-info-freq=1234")) - require.Equal(t, uint(1234), cfg.CannonInfoFreq) + require.Equal(t, uint(1234), cfg.Cannon.InfoFreq) })   t.Run("Invalid", func(t *testing.T) { @@ -653,7 +653,7 @@ delete(args, "--cannon-network") delete(args, "--game-factory-address") args["--network"] = "op-sepolia" cfg := configForArgs(t, toArgList(args)) - require.Equal(t, "op-sepolia", cfg.CannonNetwork) + require.Equal(t, "op-sepolia", cfg.Cannon.Network) })   t.Run("MustNotSpecifyNetworkAndCannonNetwork", func(t *testing.T) { @@ -663,7 +663,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-network", "--cannon-network", testNetwork)) - require.Equal(t, testNetwork, cfg.CannonNetwork) + require.Equal(t, testNetwork, cfg.Cannon.Network) }) })   @@ -674,7 +674,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) - require.Equal(t, "rollup.json", cfg.CannonRollupConfigPath) + require.Equal(t, "rollup.json", cfg.Cannon.RollupConfigPath) }) })   @@ -685,7 +685,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) - require.Equal(t, "genesis.json", cfg.CannonL2GenesisPath) + require.Equal(t, "genesis.json", cfg.Cannon.L2GenesisPath) }) }) } @@ -729,7 +729,7 @@ })   t.Run("Valid", func(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--game-window=1m")) - require.Equal(t, time.Duration(time.Minute), cfg.GameWindow) + require.Equal(t, time.Minute, cfg.GameWindow) })   t.Run("ParsesDefault", func(t *testing.T) {
diff --git OP/op-challenger/config/config.go CELO/op-challenger/config/config.go index 9586eb54906b257816b9913cd0f5bd47a38d9655..3c6f8c846aff9fc0f79e7f4e663da1bcb1a66fdf 100644 --- OP/op-challenger/config/config.go +++ CELO/op-challenger/config/config.go @@ -8,12 +8,12 @@ "runtime" "slices" "time"   - "github.com/ethereum/go-ethereum/common" - + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-node/chaincfg" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/common" )   var ( @@ -129,26 +129,14 @@ L2Rpc string // L2 RPC Url   // Specific to the cannon trace provider - CannonBin string // Path to the cannon executable to run when generating trace data - CannonServer string // Path to the op-program executable that provides the pre-image oracle server + Cannon vm.Config CannonAbsolutePreState string // File to load the absolute pre-state for Cannon traces from CannonAbsolutePreStateBaseURL *url.URL // Base URL to retrieve absolute pre-states for Cannon traces from - CannonNetwork string - CannonRollupConfigPath string - CannonL2GenesisPath string - CannonSnapshotFreq uint // Frequency of snapshots to create when executing cannon (in VM instructions) - CannonInfoFreq uint // Frequency of cannon progress log messages (in VM instructions)   // Specific to the asterisc trace provider - AsteriscBin string // Path to the asterisc executable to run when generating trace data - AsteriscServer string // Path to the op-program executable that provides the pre-image oracle server + Asterisc vm.Config AsteriscAbsolutePreState string // File to load the absolute pre-state for Asterisc traces from AsteriscAbsolutePreStateBaseURL *url.URL // Base URL to retrieve absolute pre-states for Asterisc traces from - AsteriscNetwork string - AsteriscRollupConfigPath string - AsteriscL2GenesisPath string - AsteriscSnapshotFreq uint // Frequency of snapshots to create when executing asterisc (in VM instructions) - AsteriscInfoFreq uint // Frequency of asterisc progress log messages (in VM instructions)   MaxPendingTx uint64 // Maximum number of pending transactions (0 == no limit)   @@ -185,11 +173,23 @@ PprofConfig: oppprof.DefaultCLIConfig(),   Datadir: datadir,   - CannonSnapshotFreq: DefaultCannonSnapshotFreq, - CannonInfoFreq: DefaultCannonInfoFreq, - AsteriscSnapshotFreq: DefaultAsteriscSnapshotFreq, - AsteriscInfoFreq: DefaultAsteriscInfoFreq, - GameWindow: DefaultGameWindow, + Cannon: vm.Config{ + VmType: TraceTypeCannon.String(), + L1: l1EthRpc, + L1Beacon: l1BeaconApi, + L2: l2EthRpc, + SnapshotFreq: DefaultCannonSnapshotFreq, + InfoFreq: DefaultCannonInfoFreq, + }, + Asterisc: vm.Config{ + VmType: TraceTypeAsterisc.String(), + L1: l1EthRpc, + L1Beacon: l1BeaconApi, + L2: l2EthRpc, + SnapshotFreq: DefaultAsteriscSnapshotFreq, + InfoFreq: DefaultAsteriscInfoFreq, + }, + GameWindow: DefaultGameWindow, } }   @@ -223,28 +223,28 @@ if c.MaxConcurrency == 0 { return ErrMaxConcurrencyZero } if c.TraceTypeEnabled(TraceTypeCannon) || c.TraceTypeEnabled(TraceTypePermissioned) { - if c.CannonBin == "" { + if c.Cannon.VmBin == "" { return ErrMissingCannonBin } - if c.CannonServer == "" { + if c.Cannon.Server == "" { return ErrMissingCannonServer } - if c.CannonNetwork == "" { - if c.CannonRollupConfigPath == "" { + if c.Cannon.Network == "" { + if c.Cannon.RollupConfigPath == "" { return ErrMissingCannonRollupConfig } - if c.CannonL2GenesisPath == "" { + if c.Cannon.L2GenesisPath == "" { return ErrMissingCannonL2Genesis } } else { - if c.CannonRollupConfigPath != "" { + if c.Cannon.RollupConfigPath != "" { return ErrCannonNetworkAndRollupConfig } - if c.CannonL2GenesisPath != "" { + if c.Cannon.L2GenesisPath != "" { return ErrCannonNetworkAndL2Genesis } - if ch := chaincfg.ChainByName(c.CannonNetwork); ch == nil { - return fmt.Errorf("%w: %v", ErrCannonNetworkUnknown, c.CannonNetwork) + if ch := chaincfg.ChainByName(c.Cannon.Network); ch == nil { + return fmt.Errorf("%w: %v", ErrCannonNetworkUnknown, c.Cannon.Network) } } if c.CannonAbsolutePreState == "" && c.CannonAbsolutePreStateBaseURL == nil { @@ -253,36 +253,36 @@ } if c.CannonAbsolutePreState != "" && c.CannonAbsolutePreStateBaseURL != nil { return ErrCannonAbsolutePreStateAndBaseURL } - if c.CannonSnapshotFreq == 0 { + if c.Cannon.SnapshotFreq == 0 { return ErrMissingCannonSnapshotFreq } - if c.CannonInfoFreq == 0 { + if c.Cannon.InfoFreq == 0 { return ErrMissingCannonInfoFreq } } if c.TraceTypeEnabled(TraceTypeAsterisc) { - if c.AsteriscBin == "" { + if c.Asterisc.VmBin == "" { return ErrMissingAsteriscBin } - if c.AsteriscServer == "" { + if c.Asterisc.Server == "" { return ErrMissingAsteriscServer } - if c.AsteriscNetwork == "" { - if c.AsteriscRollupConfigPath == "" { + if c.Asterisc.Network == "" { + if c.Asterisc.RollupConfigPath == "" { return ErrMissingAsteriscRollupConfig } - if c.AsteriscL2GenesisPath == "" { + if c.Asterisc.L2GenesisPath == "" { return ErrMissingAsteriscL2Genesis } } else { - if c.AsteriscRollupConfigPath != "" { + if c.Asterisc.RollupConfigPath != "" { return ErrAsteriscNetworkAndRollupConfig } - if c.AsteriscL2GenesisPath != "" { + if c.Asterisc.L2GenesisPath != "" { return ErrAsteriscNetworkAndL2Genesis } - if ch := chaincfg.ChainByName(c.AsteriscNetwork); ch == nil { - return fmt.Errorf("%w: %v", ErrAsteriscNetworkUnknown, c.AsteriscNetwork) + if ch := chaincfg.ChainByName(c.Asterisc.Network); ch == nil { + return fmt.Errorf("%w: %v", ErrAsteriscNetworkUnknown, c.Asterisc.Network) } } if c.AsteriscAbsolutePreState == "" && c.AsteriscAbsolutePreStateBaseURL == nil { @@ -291,10 +291,10 @@ } if c.AsteriscAbsolutePreState != "" && c.AsteriscAbsolutePreStateBaseURL != nil { return ErrAsteriscAbsolutePreStateAndBaseURL } - if c.AsteriscSnapshotFreq == 0 { + if c.Asterisc.SnapshotFreq == 0 { return ErrMissingAsteriscSnapshotFreq } - if c.AsteriscInfoFreq == 0 { + if c.Asterisc.InfoFreq == 0 { return ErrMissingAsteriscInfoFreq } }
diff --git OP/op-challenger/config/config_test.go CELO/op-challenger/config/config_test.go index 297bc60b97dc35f3c5c9f2f1d591426c0d3725ad..6cfae373277ce11e30994ae26642e09a236b5fa1 100644 --- OP/op-challenger/config/config_test.go +++ CELO/op-challenger/config/config_test.go @@ -36,17 +36,17 @@ var cannonTraceTypes = []TraceType{TraceTypeCannon, TraceTypePermissioned} var asteriscTraceTypes = []TraceType{TraceTypeAsterisc}   func applyValidConfigForCannon(cfg *Config) { - cfg.CannonBin = validCannonBin - cfg.CannonServer = validCannonOpProgramBin + cfg.Cannon.VmBin = validCannonBin + cfg.Cannon.Server = validCannonOpProgramBin cfg.CannonAbsolutePreStateBaseURL = validCannonAbsolutPreStateBaseURL - cfg.CannonNetwork = validCannonNetwork + cfg.Cannon.Network = validCannonNetwork }   func applyValidConfigForAsterisc(cfg *Config) { - cfg.AsteriscBin = validAsteriscBin - cfg.AsteriscServer = validAsteriscOpProgramBin + cfg.Asterisc.VmBin = validAsteriscBin + cfg.Asterisc.Server = validAsteriscOpProgramBin cfg.AsteriscAbsolutePreStateBaseURL = validAsteriscAbsolutPreStateBaseURL - cfg.AsteriscNetwork = validAsteriscNetwork + cfg.Asterisc.Network = validAsteriscNetwork }   func validConfig(traceType TraceType) Config { @@ -115,13 +115,13 @@ traceType := traceType   t.Run(fmt.Sprintf("TestCannonBinRequired-%v", traceType), func(t *testing.T) { config := validConfig(traceType) - config.CannonBin = "" + config.Cannon.VmBin = "" require.ErrorIs(t, config.Check(), ErrMissingCannonBin) })   t.Run(fmt.Sprintf("TestCannonServerRequired-%v", traceType), func(t *testing.T) { config := validConfig(traceType) - config.CannonServer = "" + config.Cannon.Server = "" require.ErrorIs(t, config.Check(), ErrMissingCannonServer) })   @@ -162,7 +162,7 @@ t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", traceType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { cfg := validConfig(traceType) - cfg.CannonSnapshotFreq = 0 + cfg.Cannon.SnapshotFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingCannonSnapshotFreq) }) }) @@ -170,46 +170,46 @@ t.Run(fmt.Sprintf("TestCannonInfoFreq-%v", traceType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { cfg := validConfig(traceType) - cfg.CannonInfoFreq = 0 + cfg.Cannon.InfoFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingCannonInfoFreq) }) })   t.Run(fmt.Sprintf("TestCannonNetworkOrRollupConfigRequired-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.CannonNetwork = "" - cfg.CannonRollupConfigPath = "" - cfg.CannonL2GenesisPath = "genesis.json" + cfg.Cannon.Network = "" + cfg.Cannon.RollupConfigPath = "" + cfg.Cannon.L2GenesisPath = "genesis.json" require.ErrorIs(t, cfg.Check(), ErrMissingCannonRollupConfig) })   t.Run(fmt.Sprintf("TestCannonNetworkOrL2GenesisRequired-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.CannonNetwork = "" - cfg.CannonRollupConfigPath = "foo.json" - cfg.CannonL2GenesisPath = "" + cfg.Cannon.Network = "" + cfg.Cannon.RollupConfigPath = "foo.json" + cfg.Cannon.L2GenesisPath = "" require.ErrorIs(t, cfg.Check(), ErrMissingCannonL2Genesis) })   t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.CannonNetwork = validCannonNetwork - cfg.CannonRollupConfigPath = "foo.json" - cfg.CannonL2GenesisPath = "" + cfg.Cannon.Network = validCannonNetwork + cfg.Cannon.RollupConfigPath = "foo.json" + cfg.Cannon.L2GenesisPath = "" require.ErrorIs(t, cfg.Check(), ErrCannonNetworkAndRollupConfig) })   t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndL2Genesis-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.CannonNetwork = validCannonNetwork - cfg.CannonRollupConfigPath = "" - cfg.CannonL2GenesisPath = "foo.json" + cfg.Cannon.Network = validCannonNetwork + cfg.Cannon.RollupConfigPath = "" + cfg.Cannon.L2GenesisPath = "foo.json" require.ErrorIs(t, cfg.Check(), ErrCannonNetworkAndL2Genesis) })   t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.CannonNetwork = "unknown" + cfg.Cannon.Network = "unknown" require.ErrorIs(t, cfg.Check(), ErrCannonNetworkUnknown) }) } @@ -221,13 +221,13 @@ traceType := traceType   t.Run(fmt.Sprintf("TestAsteriscBinRequired-%v", traceType), func(t *testing.T) { config := validConfig(traceType) - config.AsteriscBin = "" + config.Asterisc.VmBin = "" require.ErrorIs(t, config.Check(), ErrMissingAsteriscBin) })   t.Run(fmt.Sprintf("TestAsteriscServerRequired-%v", traceType), func(t *testing.T) { config := validConfig(traceType) - config.AsteriscServer = "" + config.Asterisc.Server = "" require.ErrorIs(t, config.Check(), ErrMissingAsteriscServer) })   @@ -268,7 +268,7 @@ t.Run(fmt.Sprintf("TestAsteriscSnapshotFreq-%v", traceType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { cfg := validConfig(traceType) - cfg.AsteriscSnapshotFreq = 0 + cfg.Asterisc.SnapshotFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscSnapshotFreq) }) }) @@ -276,46 +276,46 @@ t.Run(fmt.Sprintf("TestAsteriscInfoFreq-%v", traceType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { cfg := validConfig(traceType) - cfg.AsteriscInfoFreq = 0 + cfg.Asterisc.InfoFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscInfoFreq) }) })   t.Run(fmt.Sprintf("TestAsteriscNetworkOrRollupConfigRequired-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.AsteriscNetwork = "" - cfg.AsteriscRollupConfigPath = "" - cfg.AsteriscL2GenesisPath = "genesis.json" + cfg.Asterisc.Network = "" + cfg.Asterisc.RollupConfigPath = "" + cfg.Asterisc.L2GenesisPath = "genesis.json" require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscRollupConfig) })   t.Run(fmt.Sprintf("TestAsteriscNetworkOrL2GenesisRequired-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.AsteriscNetwork = "" - cfg.AsteriscRollupConfigPath = "foo.json" - cfg.AsteriscL2GenesisPath = "" + cfg.Asterisc.Network = "" + cfg.Asterisc.RollupConfigPath = "foo.json" + cfg.Asterisc.L2GenesisPath = "" require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscL2Genesis) })   t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.AsteriscNetwork = validAsteriscNetwork - cfg.AsteriscRollupConfigPath = "foo.json" - cfg.AsteriscL2GenesisPath = "" + cfg.Asterisc.Network = validAsteriscNetwork + cfg.Asterisc.RollupConfigPath = "foo.json" + cfg.Asterisc.L2GenesisPath = "" require.ErrorIs(t, cfg.Check(), ErrAsteriscNetworkAndRollupConfig) })   t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndL2Genesis-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.AsteriscNetwork = validAsteriscNetwork - cfg.AsteriscRollupConfigPath = "" - cfg.AsteriscL2GenesisPath = "foo.json" + cfg.Asterisc.Network = validAsteriscNetwork + cfg.Asterisc.RollupConfigPath = "" + cfg.Asterisc.L2GenesisPath = "foo.json" require.ErrorIs(t, cfg.Check(), ErrAsteriscNetworkAndL2Genesis) })   t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) - cfg.AsteriscNetwork = "unknown" + cfg.Asterisc.Network = "unknown" require.ErrorIs(t, cfg.Check(), ErrAsteriscNetworkUnknown) }) } @@ -404,9 +404,9 @@ cfg.RollupRpc = validRollupRpc require.NoError(t, cfg.Check())   // Require cannon specific args - cfg.CannonBin = "" + cfg.Cannon.VmBin = "" require.ErrorIs(t, cfg.Check(), ErrMissingCannonBin) - cfg.CannonBin = validCannonBin + cfg.Cannon.VmBin = validCannonBin   // Require asterisc specific args cfg.AsteriscAbsolutePreState = "" @@ -415,9 +415,9 @@ require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscAbsolutePreState) cfg.AsteriscAbsolutePreState = validAsteriscAbsolutPreState   // Require cannon specific args - cfg.AsteriscServer = "" + cfg.Asterisc.Server = "" require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscServer) - cfg.AsteriscServer = validAsteriscOpProgramBin + cfg.Asterisc.Server = validAsteriscOpProgramBin   // Check final config is valid require.NoError(t, cfg.Check())
diff --git OP/op-challenger/flags/flags.go CELO/op-challenger/flags/flags.go index 5c2c6ea199599cbb1819ac273a2623e994b4b4a5..726501ec65ec954d27bdeadd6fc130f3ef0fd516 100644 --- OP/op-challenger/flags/flags.go +++ CELO/op-challenger/flags/flags.go @@ -7,6 +7,7 @@ "runtime" "slices" "strings"   + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-service/flags" "github.com/ethereum-optimism/superchain-registry/superchain" "github.com/ethereum/go-ethereum/common" @@ -496,39 +497,53 @@ asteriscNetwork := ctx.String(AsteriscNetworkFlag.Name) if ctx.IsSet(flags.NetworkFlagName) { asteriscNetwork = ctx.String(flags.NetworkFlagName) } + l1EthRpc := ctx.String(L1EthRpcFlag.Name) + l1Beacon := ctx.String(L1BeaconFlag.Name) return &config.Config{ // Required Flags - L1EthRpc: ctx.String(L1EthRpcFlag.Name), - L1Beacon: ctx.String(L1BeaconFlag.Name), - TraceTypes: traceTypes, - GameFactoryAddress: gameFactoryAddress, - GameAllowlist: allowedGames, - GameWindow: ctx.Duration(GameWindowFlag.Name), - MaxConcurrency: maxConcurrency, - L2Rpc: l2Rpc, - MaxPendingTx: ctx.Uint64(MaxPendingTransactionsFlag.Name), - PollInterval: ctx.Duration(HTTPPollInterval.Name), - AdditionalBondClaimants: claimants, - RollupRpc: ctx.String(RollupRpcFlag.Name), - CannonNetwork: cannonNetwork, - CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name), - CannonL2GenesisPath: ctx.String(CannonL2GenesisFlag.Name), - CannonBin: ctx.String(CannonBinFlag.Name), - CannonServer: ctx.String(CannonServerFlag.Name), - CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), - CannonAbsolutePreStateBaseURL: cannonPrestatesURL, - Datadir: ctx.String(DatadirFlag.Name), - CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), - CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), - AsteriscNetwork: asteriscNetwork, - AsteriscRollupConfigPath: ctx.String(AsteriscRollupConfigFlag.Name), - AsteriscL2GenesisPath: ctx.String(AsteriscL2GenesisFlag.Name), - AsteriscBin: ctx.String(AsteriscBinFlag.Name), - AsteriscServer: ctx.String(AsteriscServerFlag.Name), + L1EthRpc: l1EthRpc, + L1Beacon: l1Beacon, + TraceTypes: traceTypes, + GameFactoryAddress: gameFactoryAddress, + GameAllowlist: allowedGames, + GameWindow: ctx.Duration(GameWindowFlag.Name), + MaxConcurrency: maxConcurrency, + L2Rpc: l2Rpc, + MaxPendingTx: ctx.Uint64(MaxPendingTransactionsFlag.Name), + PollInterval: ctx.Duration(HTTPPollInterval.Name), + AdditionalBondClaimants: claimants, + RollupRpc: ctx.String(RollupRpcFlag.Name), + Cannon: vm.Config{ + VmType: config.TraceTypeCannon.String(), + L1: l1EthRpc, + L1Beacon: l1Beacon, + L2: l2Rpc, + VmBin: ctx.String(CannonBinFlag.Name), + Server: ctx.String(CannonServerFlag.Name), + Network: cannonNetwork, + RollupConfigPath: ctx.String(CannonRollupConfigFlag.Name), + L2GenesisPath: ctx.String(CannonL2GenesisFlag.Name), + SnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), + InfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), + }, + CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), + CannonAbsolutePreStateBaseURL: cannonPrestatesURL, + Datadir: ctx.String(DatadirFlag.Name), + Asterisc: vm.Config{ + VmType: config.TraceTypeAsterisc.String(), + L1: l1EthRpc, + L1Beacon: l1Beacon, + L2: l2Rpc, + VmBin: ctx.String(AsteriscBinFlag.Name), + Server: ctx.String(AsteriscServerFlag.Name), + Network: asteriscNetwork, + RollupConfigPath: ctx.String(AsteriscRollupConfigFlag.Name), + L2GenesisPath: ctx.String(AsteriscL2GenesisFlag.Name), + SnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), + InfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), + }, AsteriscAbsolutePreState: ctx.String(AsteriscPreStateFlag.Name), AsteriscAbsolutePreStateBaseURL: asteriscPreStatesURL, - AsteriscSnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), - AsteriscInfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), TxMgrConfig: txMgrConfig, MetricsConfig: metricsConfig, PprofConfig: pprofConfig,
diff --git OP/op-challenger/game/fault/register.go CELO/op-challenger/game/fault/register.go index cb23266a2b1236e5bfab45c89cf72562704c1c5f..7478dd4b23fedd092c184d8fef574c68e27b7dbf 100644 --- OP/op-challenger/game/fault/register.go +++ CELO/op-challenger/game/fault/register.go @@ -254,7 +254,7 @@ asteriscPrestate, err := prestateSource.PrestatePath(requiredPrestatehash) if err != nil { return nil, fmt.Errorf("failed to get asterisc prestate: %w", err) } - accessor, err := outputs.NewOutputAsteriscTraceAccessor(logger, m, cfg, l2Client, prestateProvider, asteriscPrestate, rollupClient, dir, l1HeadID, splitDepth, prestateBlock, poststateBlock) + accessor, err := outputs.NewOutputAsteriscTraceAccessor(logger, m, cfg.Asterisc, l2Client, prestateProvider, asteriscPrestate, rollupClient, dir, l1HeadID, splitDepth, prestateBlock, poststateBlock) if err != nil { return nil, err } @@ -349,7 +349,7 @@ cannonPrestate, err := prestateSource.PrestatePath(requiredPrestatehash) if err != nil { return nil, fmt.Errorf("failed to get cannon prestate: %w", err) } - accessor, err := outputs.NewOutputCannonTraceAccessor(logger, m, cfg, l2Client, prestateProvider, cannonPrestate, rollupClient, dir, l1HeadID, splitDepth, prestateBlock, poststateBlock) + accessor, err := outputs.NewOutputCannonTraceAccessor(logger, m, cfg.Cannon, l2Client, prestateProvider, cannonPrestate, rollupClient, dir, l1HeadID, splitDepth, prestateBlock, poststateBlock) if err != nil { return nil, err }
diff --git OP/op-challenger/game/monitor.go CELO/op-challenger/game/monitor.go index fe263a661692e84d2e0282b6e64485be241035ad..dbdcc26ab81c7343a271f757973b5b9348166a45 100644 --- OP/op-challenger/game/monitor.go +++ CELO/op-challenger/game/monitor.go @@ -19,8 +19,6 @@ "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" )   -type blockNumberFetcher func(ctx context.Context) (uint64, error) - // gameSource loads information about the games available to play type gameSource interface { GetGamesAtOrAfter(ctx context.Context, blockHash common.Hash, earliestTimestamp uint64) ([]types.GameMetadata, error) @@ -44,18 +42,17 @@ Schedule(blockNumber uint64, games []types.GameMetadata) error }   type gameMonitor struct { - logger log.Logger - clock RWClock - source gameSource - scheduler gameScheduler - preimages preimageScheduler - gameWindow time.Duration - claimer claimer - fetchBlockNumber blockNumberFetcher - allowedGames []common.Address - l1HeadsSub ethereum.Subscription - l1Source *headSource - runState sync.Mutex + logger log.Logger + clock RWClock + source gameSource + scheduler gameScheduler + preimages preimageScheduler + gameWindow time.Duration + claimer claimer + allowedGames []common.Address + l1HeadsSub ethereum.Subscription + l1Source *headSource + runState sync.Mutex }   type MinimalSubscriber interface { @@ -78,21 +75,19 @@ scheduler gameScheduler, preimages preimageScheduler, gameWindow time.Duration, claimer claimer, - fetchBlockNumber blockNumberFetcher, allowedGames []common.Address, l1Source MinimalSubscriber, ) *gameMonitor { return &gameMonitor{ - logger: logger, - clock: cl, - scheduler: scheduler, - preimages: preimages, - source: source, - gameWindow: gameWindow, - claimer: claimer, - fetchBlockNumber: fetchBlockNumber, - allowedGames: allowedGames, - l1Source: &headSource{inner: l1Source}, + logger: logger, + clock: cl, + scheduler: scheduler, + preimages: preimages, + source: source, + gameWindow: gameWindow, + claimer: claimer, + allowedGames: allowedGames, + l1Source: &headSource{inner: l1Source}, } }
diff --git OP/op-challenger/game/monitor_test.go CELO/op-challenger/game/monitor_test.go index 7a3da241aa247b429a9968479d6368edc72e817c..ff2871ed4efb7c65648b8e355eb971118f38a529 100644 --- OP/op-challenger/game/monitor_test.go +++ CELO/op-challenger/game/monitor_test.go @@ -155,11 +155,6 @@ allowedGames []common.Address, ) (*gameMonitor, *stubGameSource, *stubScheduler, *mockNewHeadSource, *stubPreimageScheduler, *mockScheduler) { logger := testlog.Logger(t, log.LevelDebug) source := &stubGameSource{} - i := uint64(1) - fetchBlockNum := func(ctx context.Context) (uint64, error) { - i++ - return i, nil - } sched := &stubScheduler{} preimages := &stubPreimageScheduler{} mockHeadSource := &mockNewHeadSource{} @@ -172,7 +167,6 @@ sched, preimages, time.Duration(0), stubClaimer, - fetchBlockNum, allowedGames, mockHeadSource, )
diff --git OP/op-challenger/game/service.go CELO/op-challenger/game/service.go index 587de9a366ab887a32ad4f48a2462741a3357334..fea6bc2aaf63c603cba4d5b13010108269842ecf 100644 --- OP/op-challenger/game/service.go +++ CELO/op-challenger/game/service.go @@ -251,7 +251,7 @@ return nil }   func (s *Service) initMonitor(cfg *config.Config) { - s.monitor = newGameMonitor(s.logger, s.l1Clock, s.factoryContract, s.sched, s.preimages, cfg.GameWindow, s.claimer, s.l1Client.BlockNumber, cfg.GameAllowlist, s.pollClient) + s.monitor = newGameMonitor(s.logger, s.l1Clock, s.factoryContract, s.sched, s.preimages, cfg.GameWindow, s.claimer, cfg.GameAllowlist, s.pollClient) }   func (s *Service) Start(ctx context.Context) error { @@ -280,6 +280,11 @@ } } if s.monitor != nil { s.monitor.StopMonitoring() + } + if s.claimer != nil { + if err := s.claimer.Close(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to close claimer: %w", err)) + } } if s.faultGamesCloser != nil { s.faultGamesCloser()
diff --git OP/op-challenger/metrics/metrics.go CELO/op-challenger/metrics/metrics.go index 9253f26cc63146d1d3d8f959c766d0900df04763..c46edcd67fccde32bbc6e36397be6a1da445d7e1 100644 --- OP/op-challenger/metrics/metrics.go +++ CELO/op-challenger/metrics/metrics.go @@ -2,6 +2,7 @@ package metrics   import ( "io" + "time"   "github.com/ethereum-optimism/optimism/op-service/httputil" "github.com/ethereum-optimism/optimism/op-service/sources/caching" @@ -37,8 +38,7 @@ RecordGameStep() RecordGameMove() RecordGameL2Challenge() - RecordCannonExecutionTime(t float64) - RecordAsteriscExecutionTime(t float64) + RecordVmExecutionTime(vmType string, t time.Duration) RecordClaimResolutionTime(t float64) RecordGameActTime(t float64)   @@ -88,10 +88,9 @@ moves prometheus.Counter steps prometheus.Counter l2Challenges prometheus.Counter   - claimResolutionTime prometheus.Histogram - gameActTime prometheus.Histogram - cannonExecutionTime prometheus.Histogram - asteriscExecutionTime prometheus.Histogram + claimResolutionTime prometheus.Histogram + gameActTime prometheus.Histogram + vmExecutionTime *prometheus.HistogramVec   trackedGames prometheus.GaugeVec inflightGames prometheus.Gauge @@ -152,14 +151,6 @@ Namespace: Namespace, Name: "l2_challenges", Help: "Number of L2 challenges made by the challenge agent", }), - cannonExecutionTime: factory.NewHistogram(prometheus.HistogramOpts{ - Namespace: Namespace, - Name: "cannon_execution_time", - Help: "Time (in seconds) to execute cannon", - Buckets: append( - []float64{1.0, 10.0}, - prometheus.ExponentialBuckets(30.0, 2.0, 14)...), - }), claimResolutionTime: factory.NewHistogram(prometheus.HistogramOpts{ Namespace: Namespace, Name: "claim_resolution_time", @@ -174,14 +165,14 @@ Buckets: append( []float64{1.0, 2.0, 5.0, 10.0}, prometheus.ExponentialBuckets(30.0, 2.0, 14)...), }), - asteriscExecutionTime: factory.NewHistogram(prometheus.HistogramOpts{ + vmExecutionTime: factory.NewHistogramVec(prometheus.HistogramOpts{ Namespace: Namespace, - Name: "asterisc_execution_time", - Help: "Time (in seconds) to execute asterisc", + Name: "vm_execution_time", + Help: "Time (in seconds) to execute the fault proof VM", Buckets: append( []float64{1.0, 10.0}, prometheus.ExponentialBuckets(30.0, 2.0, 14)...), - }), + }, []string{"vm"}), bondClaimFailures: factory.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Name: "claim_failures", @@ -278,12 +269,8 @@ func (m *Metrics) RecordBondClaimed(amount uint64) { m.bondsClaimed.Add(float64(amount)) }   -func (m *Metrics) RecordCannonExecutionTime(t float64) { - m.cannonExecutionTime.Observe(t) -} - -func (m *Metrics) RecordAsteriscExecutionTime(t float64) { - m.asteriscExecutionTime.Observe(t) +func (m *Metrics) RecordVmExecutionTime(vmType string, dur time.Duration) { + m.vmExecutionTime.WithLabelValues(vmType).Observe(dur.Seconds()) }   func (m *Metrics) RecordClaimResolutionTime(t float64) {
diff --git OP/op-challenger/metrics/noop.go CELO/op-challenger/metrics/noop.go index c238f03fcb7338370ed40efcd5c25f04d53748a3..fc0f6d077803bc8950f30963a274cb8c107608aa 100644 --- OP/op-challenger/metrics/noop.go +++ CELO/op-challenger/metrics/noop.go @@ -2,6 +2,7 @@ package metrics   import ( "io" + "time"   contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum/go-ethereum/common" @@ -37,10 +38,9 @@ func (*NoopMetricsImpl) RecordBondClaimFailed() {} func (*NoopMetricsImpl) RecordBondClaimed(uint64) {}   -func (*NoopMetricsImpl) RecordCannonExecutionTime(t float64) {} -func (*NoopMetricsImpl) RecordAsteriscExecutionTime(t float64) {} -func (*NoopMetricsImpl) RecordClaimResolutionTime(t float64) {} -func (*NoopMetricsImpl) RecordGameActTime(t float64) {} +func (*NoopMetricsImpl) RecordVmExecutionTime(_ string, _ time.Duration) {} +func (*NoopMetricsImpl) RecordClaimResolutionTime(t float64) {} +func (*NoopMetricsImpl) RecordGameActTime(t float64) {}   func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {}
diff --git OP/op-e2e/actions/l2_batcher.go CELO/op-e2e/actions/l2_batcher.go index 310a6cade9516d26a8e55cc3273aca494eb7368b..fd249e58fe6ab2638ba2fec7b8abda9c6c796aa7 100644 --- OP/op-e2e/actions/l2_batcher.go +++ CELO/op-e2e/actions/l2_batcher.go @@ -277,7 +277,7 @@ for _, opt := range txOpts { opt(rawTx) }   - gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false, nil) require.NoError(t, err, "need to compute intrinsic gas") rawTx.Gas = gas txData = rawTx @@ -468,7 +468,7 @@ GasTipCap: gasTipCap, GasFeeCap: gasFeeCap, Data: outputFrame, } - gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false, nil) require.NoError(t, err, "need to compute intrinsic gas") rawTx.Gas = gas
diff --git OP/op-e2e/actions/l2_batcher_test.go CELO/op-e2e/actions/l2_batcher_test.go index 3a137ce992af6458cc10688ec0a2b8fdfaf697b2..371dfe8a02ff0e1a0723316f025a3aebb7d186ea 100644 --- OP/op-e2e/actions/l2_batcher_test.go +++ CELO/op-e2e/actions/l2_batcher_test.go @@ -17,6 +17,7 @@ batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/rollup/finality" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" @@ -246,7 +247,7 @@ // Now try to finalize block 4, but with a bad/malicious alternative hash. // If we get this false signal, we shouldn't finalize the L2 chain. altBlock4 := sequencer.SyncStatus().SafeL1 altBlock4.Hash = common.HexToHash("0xdead") - sequencer.finalizer.Finalize(t.Ctx(), altBlock4) + sequencer.synchronousEvents.Emit(finality.FinalizeL1Event{FinalizedL1: altBlock4}) sequencer.ActL2PipelineFull(t) require.Equal(t, uint64(3), sequencer.SyncStatus().FinalizedL1.Number) require.Equal(t, heightToSubmit, sequencer.SyncStatus().FinalizedL2.Number, "unknown/bad finalized L1 blocks are ignored") @@ -496,7 +497,7 @@ signer := types.LatestSigner(sd.L2Cfg.Config) data := make([]byte, 120_000) // very large L2 txs, as large as the tx-pool will accept _, err := rng.Read(data[:]) // fill with random bytes, to make compression ineffective require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) require.NoError(t, err) if gas > engine.engineApi.RemainingBlockGas() { break
diff --git OP/op-e2e/actions/l2_proposer_test.go CELO/op-e2e/actions/l2_proposer_test.go index 3de913f8e50ae833e1f1bbb48e58f4b62a427632..ba1f1d3640c0a4f388671ab054c69f9c3c7c9128 100644 --- OP/op-e2e/actions/l2_proposer_test.go +++ CELO/op-e2e/actions/l2_proposer_test.go @@ -96,6 +96,7 @@ // derive and see the L2 chain fully finalize sequencer.ActL2PipelineFull(t) sequencer.ActL1SafeSignal(t) sequencer.ActL1FinalizedSignal(t) + sequencer.ActL2PipelineFull(t) require.Equal(t, sequencer.SyncStatus().UnsafeL2, sequencer.SyncStatus().FinalizedL2) require.True(t, proposer.CanPropose(t))
diff --git OP/op-e2e/actions/l2_sequencer.go CELO/op-e2e/actions/l2_sequencer.go index fdbfb89b3426d3308a2456a0562f2c7a867d6be1..dd3a795e96ca26b09b645897d6e2cf61e6a62882 100644 --- OP/op-e2e/actions/l2_sequencer.go +++ CELO/op-e2e/actions/l2_sequencer.go @@ -34,7 +34,7 @@ // L2Sequencer is an actor that functions like a rollup node, // without the full P2P/API/Node stack, but just the derivation state, and simplified driver with sequencing ability. type L2Sequencer struct { - L2Verifier + *L2Verifier   sequencer *driver.Sequencer   @@ -52,7 +52,7 @@ l1OriginSelector := &MockL1OriginSelector{ actual: driver.NewL1OriginSelector(log, cfg, seqConfDepthL1), } return &L2Sequencer{ - L2Verifier: *ver, + L2Verifier: ver, sequencer: driver.NewSequencer(log, cfg, ver.engine, attrBuilder, l1OriginSelector, metrics.NoopMetrics), mockL1OriginSelector: l1OriginSelector, failL2GossipUnsafeBlock: nil,
diff --git OP/op-e2e/actions/l2_verifier.go CELO/op-e2e/actions/l2_verifier.go index a0f513cfba38672c66969d00b1bf13c60977d72b..95fd95eaf0c0b6c75f5169ec27b149e82c41adcc 100644 --- OP/op-e2e/actions/l2_verifier.go +++ CELO/op-e2e/actions/l2_verifier.go @@ -3,6 +3,7 @@ import ( "context" "errors" + "fmt" "io"   "github.com/ethereum/go-ethereum/common" @@ -22,6 +23,7 @@ "github.com/ethereum-optimism/optimism/op-node/rollup/finality" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/safego" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/testutils" ) @@ -36,6 +38,8 @@ engine.Engine L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2BlockRef, error) }   + synchronousEvents *rollup.SynchronousEvents + syncDeriver *driver.SyncDeriver   // L2 rollup @@ -43,10 +47,9 @@ engine *engine.EngineController derivation *derive.DerivationPipeline clSync *clsync.CLSync   - attributesHandler driver.AttributesHandler - safeHeadListener rollup.SafeHeadListener - finalizer driver.Finalizer - syncCfg *sync.Config + safeHeadListener rollup.SafeHeadListener + finalizer driver.Finalizer + syncCfg *sync.Config   l1 derive.L1Fetcher l1State *driver.L1State @@ -59,6 +62,10 @@ rpc *rpc.Server   failRPC error // mock error + + // The L2Verifier actor is embedded in the L2Sequencer actor, + // but must not be copied for the deriver-functionality to modify the same state. + _ safego.NoCopy }   type L2API interface { @@ -76,47 +83,78 @@ node.SafeDBReader }   func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, blobsSrc derive.L1BlobsFetcher, plasmaSrc driver.PlasmaIface, eng L2API, cfg *rollup.Config, syncCfg *sync.Config, safeHeadListener safeDB) *L2Verifier { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + rootDeriver := &rollup.SynchronousDerivers{} + synchronousEvents := rollup.NewSynchronousEvents(log, ctx, rootDeriver) + metrics := &testutils.TestDerivationMetrics{} - engine := engine.NewEngineController(eng, log, metrics, cfg, syncCfg.SyncMode) + ec := engine.NewEngineController(eng, log, metrics, cfg, syncCfg.SyncMode, synchronousEvents) + engineResetDeriver := engine.NewEngineResetDeriver(ctx, log, cfg, l1, eng, syncCfg, synchronousEvents)   - clSync := clsync.NewCLSync(log, cfg, metrics, engine) + clSync := clsync.NewCLSync(log, cfg, metrics, synchronousEvents)   var finalizer driver.Finalizer if cfg.PlasmaEnabled() { - finalizer = finality.NewPlasmaFinalizer(log, cfg, l1, engine, plasmaSrc) + finalizer = finality.NewPlasmaFinalizer(ctx, log, cfg, l1, synchronousEvents, plasmaSrc) } else { - finalizer = finality.NewFinalizer(log, cfg, l1, engine) + finalizer = finality.NewFinalizer(ctx, log, cfg, l1, synchronousEvents) }   - attributesHandler := attributes.NewAttributesHandler(log, cfg, engine, eng) + attributesHandler := attributes.NewAttributesHandler(log, cfg, ctx, eng, synchronousEvents)   pipeline := derive.NewDerivationPipeline(log, cfg, l1, blobsSrc, plasmaSrc, eng, metrics) + pipelineDeriver := derive.NewPipelineDeriver(ctx, pipeline, synchronousEvents) + + syncDeriver := &driver.SyncDeriver{ + Derivation: pipeline, + Finalizer: finalizer, + SafeHeadNotifs: safeHeadListener, + CLSync: clSync, + Engine: ec, + SyncCfg: syncCfg, + Config: cfg, + L1: l1, + L2: eng, + Emitter: synchronousEvents, + Log: log, + Ctx: ctx, + Drain: synchronousEvents.Drain, + } + + engDeriv := engine.NewEngDeriver(log, ctx, cfg, ec, synchronousEvents)   rollupNode := &L2Verifier{ log: log, eng: eng, - engine: engine, + engine: ec, clSync: clSync, derivation: pipeline, finalizer: finalizer, - attributesHandler: attributesHandler, safeHeadListener: safeHeadListener, syncCfg: syncCfg, - syncDeriver: &driver.SyncDeriver{ - Derivation: pipeline, - Finalizer: finalizer, - AttributesHandler: attributesHandler, - SafeHeadNotifs: safeHeadListener, - CLSync: clSync, - Engine: engine, - }, - l1: l1, - l1State: driver.NewL1State(log, metrics), - l2PipelineIdle: true, - l2Building: false, - rollupCfg: cfg, - rpc: rpc.NewServer(), + syncDeriver: syncDeriver, + l1: l1, + l1State: driver.NewL1State(log, metrics), + l2PipelineIdle: true, + l2Building: false, + rollupCfg: cfg, + rpc: rpc.NewServer(), + synchronousEvents: synchronousEvents, + } + + *rootDeriver = rollup.SynchronousDerivers{ + syncDeriver, + engineResetDeriver, + engDeriv, + rollupNode, + clSync, + pipelineDeriver, + attributesHandler, + finalizer, } + t.Cleanup(rollupNode.rpc.Stop)   // setup RPC server for rollup node, hooked to the actor as backend @@ -169,6 +207,10 @@ }   func (s *l2VerifierBackend) SequencerActive(ctx context.Context) (bool, error) { return false, nil +} + +func (s *l2VerifierBackend) OverrideLeader(ctx context.Context) error { + return nil }   func (s *l2VerifierBackend) OnUnsafeL2Payload(ctx context.Context, envelope *eth.ExecutionPayloadEnvelope) error { @@ -250,63 +292,76 @@ func (s *L2Verifier) ActL1FinalizedSignal(t Testing) { finalized, err := s.l1.L1BlockRefByLabel(t.Ctx(), eth.Finalized) require.NoError(t, err) s.l1State.HandleNewL1FinalizedBlock(finalized) - s.finalizer.Finalize(t.Ctx(), finalized) + s.synchronousEvents.Emit(finality.FinalizeL1Event{FinalizedL1: finalized}) +} + +func (s *L2Verifier) OnEvent(ev rollup.Event) { + switch x := ev.(type) { + case rollup.L1TemporaryErrorEvent: + s.log.Warn("L1 temporary error", "err", x.Err) + case rollup.EngineTemporaryErrorEvent: + s.log.Warn("Engine temporary error", "err", x.Err) + if errors.Is(x.Err, sync.WrongChainErr) { // action-tests don't back off on temporary errors. Avoid a bad genesis setup from looping. + panic(fmt.Errorf("genesis setup issue: %w", x.Err)) + } + case rollup.ResetEvent: + s.log.Warn("Derivation pipeline is being reset", "err", x.Err) + case rollup.CriticalErrorEvent: + panic(fmt.Errorf("derivation failed critically: %w", x.Err)) + case derive.DeriverIdleEvent: + s.l2PipelineIdle = true + } }   -// syncStep represents the Driver.syncStep -func (s *L2Verifier) syncStep(ctx context.Context) error { - return s.syncDeriver.SyncStep(ctx) +func (s *L2Verifier) ActL2EventsUntilPending(t Testing, num uint64) { + s.ActL2EventsUntil(t, func(ev rollup.Event) bool { + x, ok := ev.(engine.PendingSafeUpdateEvent) + return ok && x.PendingSafe.Number == num + }, 1000, false) }   -// ActL2PipelineStep runs one iteration of the L2 derivation pipeline -func (s *L2Verifier) ActL2PipelineStep(t Testing) { +func (s *L2Verifier) ActL2EventsUntil(t Testing, fn func(ev rollup.Event) bool, max int, excl bool) { + t.Helper() if s.l2Building { t.InvalidAction("cannot derive new data while building L2 block") return } - - err := s.syncStep(t.Ctx()) - if err == io.EOF || (err != nil && errors.Is(err, derive.EngineELSyncing)) { - s.l2PipelineIdle = true - return - } else if err != nil && errors.Is(err, derive.NotEnoughData) { - return - } else if err != nil && errors.Is(err, derive.ErrReset) { - s.log.Warn("Derivation pipeline is reset", "err", err) - s.derivation.Reset() - if err := engine.ResetEngine(t.Ctx(), s.log, s.rollupCfg, s.engine, s.l1, s.eng, s.syncCfg, s.safeHeadListener); err != nil { - s.log.Error("Derivation pipeline not ready, failed to reset engine", "err", err) - // Derivation-pipeline will return a new ResetError until we confirm the engine has been successfully reset. + for i := 0; i < max; i++ { + err := s.synchronousEvents.DrainUntil(fn, excl) + if err == nil { return } - s.derivation.ConfirmEngineReset() - return - } else if err != nil && errors.Is(err, derive.ErrTemporary) { - s.log.Warn("Derivation process temporary error", "err", err) - if errors.Is(err, sync.WrongChainErr) { // action-tests don't back off on temporary errors. Avoid a bad genesis setup from looping. - t.Fatalf("genesis setup issue: %v", err) + if err == io.EOF { + s.synchronousEvents.Emit(driver.StepEvent{}) } - return - } else if err != nil && errors.Is(err, derive.ErrCritical) { - t.Fatalf("derivation failed critically: %v", err) - } else if err != nil { - t.Fatalf("derivation failed: %v", err) - } else { - return } + t.Fatalf("event condition did not hit, ran maximum number of steps: %d", max) }   func (s *L2Verifier) ActL2PipelineFull(t Testing) { s.l2PipelineIdle = false + i := 0 for !s.l2PipelineIdle { - s.ActL2PipelineStep(t) + i += 1 + // Some tests do generate a lot of derivation steps + // (e.g. thousand blocks span-batch, or deep reorgs). + // Hence we set the sanity limit to something really high. + if i > 10_000 { + t.Fatalf("ActL2PipelineFull running for too long. Is a deriver looping?") + } + if s.l2Building { + t.InvalidAction("cannot derive new data while building L2 block") + return + } + s.syncDeriver.Emitter.Emit(driver.StepEvent{}) + require.NoError(t, s.syncDeriver.Drain(), "complete all event processing triggered by deriver step") } }   // ActL2UnsafeGossipReceive creates an action that can receive an unsafe execution payload, like gossipsub func (s *L2Verifier) ActL2UnsafeGossipReceive(payload *eth.ExecutionPayloadEnvelope) Action { return func(t Testing) { - s.clSync.AddUnsafePayload(payload) + s.synchronousEvents.Emit(clsync.ReceivedUnsafePayloadEvent{Envelope: payload}) } }
diff --git OP/op-e2e/actions/plasma_test.go CELO/op-e2e/actions/plasma_test.go index 4307cab1867790760097ea91ba746331c8043b95..d1a5606d24fb82d1d1db6e540a79e42b6039c8a1 100644 --- OP/op-e2e/actions/plasma_test.go +++ CELO/op-e2e/actions/plasma_test.go @@ -5,21 +5,24 @@ "math/big" "math/rand" "testing"   + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-node/node/safedb" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" plasma "github.com/ethereum-optimism/optimism/op-plasma" "github.com/ethereum-optimism/optimism/op-plasma/bindings" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/require" )   -// Devnet allocs should have plasma mode enabled for these tests to pass +// Devnet allocs should have alt-da mode enabled for these tests to pass   // L2PlasmaDA is a test harness for manipulating plasma DA state. type L2PlasmaDA struct { @@ -497,9 +500,13 @@ require.NoError(t, err)   // advance the pipeline until it errors out as it is still stuck // on deriving the first commitment - for i := 0; i < 3; i++ { - a.sequencer.ActL2PipelineStep(t) - } + a.sequencer.ActL2EventsUntil(t, func(ev rollup.Event) bool { + x, ok := ev.(rollup.EngineTemporaryErrorEvent) + if ok { + require.ErrorContains(t, x.Err, "failed to fetch input data") + } + return ok + }, 100, false)   // keep track of the second commitment comm2 := a.lastComm @@ -618,6 +625,7 @@ // advance derivation and finalize plasma via the L1 signal a.sequencer.ActL2PipelineFull(t) a.ActL1Finalized(t) + a.sequencer.ActL2PipelineFull(t) // finality event needs to be processed   // given 12s l1 time and 1s l2 time, l2 should be 12 * 3 = 36 blocks finalized require.Equal(t, uint64(36), a.sequencer.SyncStatus().FinalizedL2.Number)
diff --git OP/op-e2e/actions/reorg_test.go CELO/op-e2e/actions/reorg_test.go index 1b4edbaf9cfeef648cf75533260b1159cb2dcd70..27cce2aa1086e86cb18cb56236d521398154272c 100644 --- OP/op-e2e/actions/reorg_test.go +++ CELO/op-e2e/actions/reorg_test.go @@ -767,7 +767,7 @@ // give the unsafe block to the verifier, and see if it reorgs because of any unsafe inputs head, err := altSeqEngCl.PayloadByLabel(t.Ctx(), eth.Unsafe) require.NoError(t, err) - verifier.ActL2UnsafeGossipReceive(head) + verifier.ActL2UnsafeGossipReceive(head)(t)   // make sure verifier has processed everything verifier.ActL2PipelineFull(t)
diff --git OP/op-e2e/actions/span_batch_test.go CELO/op-e2e/actions/span_batch_test.go index 6ccb76a461bd6d9eab10aaf4ac2a3187f8239dbb..39dd3f817a7aef9febeb32da30d217f7eb898a34 100644 --- OP/op-e2e/actions/span_batch_test.go +++ CELO/op-e2e/actions/span_batch_test.go @@ -524,7 +524,7 @@ signer := types.LatestSigner(sd.L2Cfg.Config) data := make([]byte, rand.Intn(100)) _, err := crand.Read(data[:]) // fill with random bytes require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) require.NoError(t, err) baseFee := seqEngine.l2Chain.CurrentBlock().BaseFee nonce, err := cl.PendingNonceAt(t.Ctx(), addrs[userIdx]) @@ -663,7 +663,7 @@ signer := types.LatestSigner(sdDeltaActivated.L2Cfg.Config) data := make([]byte, rand.Intn(100)) _, err := crand.Read(data[:]) // fill with random bytes require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) require.NoError(t, err) baseFee := seqEngine.l2Chain.CurrentBlock().BaseFee nonce, err := seqEngCl.PendingNonceAt(t.Ctx(), addrs[userIdx])
diff --git OP/op-e2e/actions/sync_test.go CELO/op-e2e/actions/sync_test.go index e7521bdd8c94b0b800ca469b661074d03385f112..3cd110cf59d9ad9a25ffce84742c6220a7993210 100644 --- OP/op-e2e/actions/sync_test.go +++ CELO/op-e2e/actions/sync_test.go @@ -7,13 +7,7 @@ "math/rand" "testing" "time"   - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-node/rollup/sync" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum-optimism/optimism/op-service/testutils" + "github.com/stretchr/testify/require"   "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/beacon/engine" @@ -22,7 +16,16 @@ "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + engine2 "github.com/ethereum-optimism/optimism/op-node/rollup/engine" + "github.com/ethereum-optimism/optimism/op-node/rollup/sync" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/testutils" )   func newSpanChannelOut(t StatefulTesting, e e2eutils.SetupData) derive.ChannelOut { @@ -262,10 +265,8 @@ // before stepping, make sure backupUnsafe is empty require.Equal(t, eth.L2BlockRef{}, sequencer.L2BackupUnsafe()) // pendingSafe must not be advanced as well require.Equal(t, sequencer.L2PendingSafe().Number, uint64(0)) - // Preheat engine queue and consume A1 from batch - for i := 0; i < 4; i++ { - sequencer.ActL2PipelineStep(t) - } + // Run until we consume A1 from batch + sequencer.ActL2EventsUntilPending(t, 1) // A1 is valid original block so pendingSafe is advanced require.Equal(t, sequencer.L2PendingSafe().Number, uint64(1)) require.Equal(t, sequencer.L2Unsafe().Number, uint64(5)) @@ -273,8 +274,8 @@ // backupUnsafe is still empty require.Equal(t, eth.L2BlockRef{}, sequencer.L2BackupUnsafe())   // Process B2 - sequencer.ActL2PipelineStep(t) - sequencer.ActL2PipelineStep(t) + // Run until we consume B2 from batch + sequencer.ActL2EventsUntilPending(t, 2) // B2 is valid different block, triggering unsafe chain reorg require.Equal(t, sequencer.L2Unsafe().Number, uint64(2)) // B2 is valid different block, triggering unsafe block backup @@ -425,10 +426,8 @@ // before stepping, make sure backupUnsafe is empty require.Equal(t, eth.L2BlockRef{}, sequencer.L2BackupUnsafe()) // pendingSafe must not be advanced as well require.Equal(t, sequencer.L2PendingSafe().Number, uint64(0)) - // Preheat engine queue and consume A1 from batch - for i := 0; i < 4; i++ { - sequencer.ActL2PipelineStep(t) - } + // Run till we consumed A1 from batch + sequencer.ActL2EventsUntilPending(t, 1) // A1 is valid original block so pendingSafe is advanced require.Equal(t, sequencer.L2PendingSafe().Number, uint64(1)) require.Equal(t, sequencer.L2Unsafe().Number, uint64(5)) @@ -436,8 +435,7 @@ // backupUnsafe is still empty require.Equal(t, eth.L2BlockRef{}, sequencer.L2BackupUnsafe())   // Process B2 - sequencer.ActL2PipelineStep(t) - sequencer.ActL2PipelineStep(t) + sequencer.ActL2EventsUntilPending(t, 2) // B2 is valid different block, triggering unsafe chain reorg require.Equal(t, sequencer.L2Unsafe().Number, uint64(2)) // B2 is valid different block, triggering unsafe block backup @@ -447,14 +445,14 @@ require.Equal(t, sequencer.L2PendingSafe().Number, uint64(2))   // B3 is invalid block // NextAttributes is called - sequencer.ActL2PipelineStep(t) - // forceNextSafeAttributes is called - sequencer.ActL2PipelineStep(t) + sequencer.ActL2EventsUntil(t, func(ev rollup.Event) bool { + _, ok := ev.(engine2.ProcessAttributesEvent) + return ok + }, 100, true) // mock forkChoiceUpdate error while restoring previous unsafe chain using backupUnsafe. seqEng.ActL2RPCFail(t, eth.InputError{Inner: errors.New("mock L2 RPC error"), Code: eth.InvalidForkchoiceState})   - // TryBackupUnsafeReorg is called - sequencer.ActL2PipelineStep(t) + // The backup-unsafe rewind is applied   // try to process invalid leftovers: B4, B5 sequencer.ActL2PipelineFull(t) @@ -565,9 +563,7 @@ require.Equal(t, eth.L2BlockRef{}, sequencer.L2BackupUnsafe()) // pendingSafe must not be advanced as well require.Equal(t, sequencer.L2PendingSafe().Number, uint64(0)) // Preheat engine queue and consume A1 from batch - for i := 0; i < 4; i++ { - sequencer.ActL2PipelineStep(t) - } + sequencer.ActL2EventsUntilPending(t, 1) // A1 is valid original block so pendingSafe is advanced require.Equal(t, sequencer.L2PendingSafe().Number, uint64(1)) require.Equal(t, sequencer.L2Unsafe().Number, uint64(5)) @@ -575,8 +571,7 @@ // backupUnsafe is still empty require.Equal(t, eth.L2BlockRef{}, sequencer.L2BackupUnsafe())   // Process B2 - sequencer.ActL2PipelineStep(t) - sequencer.ActL2PipelineStep(t) + sequencer.ActL2EventsUntilPending(t, 2) // B2 is valid different block, triggering unsafe chain reorg require.Equal(t, sequencer.L2Unsafe().Number, uint64(2)) // B2 is valid different block, triggering unsafe block backup @@ -585,17 +580,21 @@ // B2 is valid different block, so pendingSafe is advanced require.Equal(t, sequencer.L2PendingSafe().Number, uint64(2))   // B3 is invalid block - // NextAttributes is called - sequencer.ActL2PipelineStep(t) - // forceNextSafeAttributes is called - sequencer.ActL2PipelineStep(t) + // wait till attributes processing (excl.) before mocking errors + sequencer.ActL2EventsUntil(t, func(ev rollup.Event) bool { + _, ok := ev.(engine2.ProcessAttributesEvent) + return ok + }, 100, true)   serverErrCnt := 2 for i := 0; i < serverErrCnt; i++ { // mock forkChoiceUpdate failure while restoring previous unsafe chain using backupUnsafe. seqEng.ActL2RPCFail(t, engine.GenericServerError) // TryBackupUnsafeReorg is called - forkChoiceUpdate returns GenericServerError so retry - sequencer.ActL2PipelineStep(t) + sequencer.ActL2EventsUntil(t, func(ev rollup.Event) bool { + _, ok := ev.(rollup.EngineTemporaryErrorEvent) + return ok + }, 100, false) // backupUnsafeHead not emptied yet require.Equal(t, targetUnsafeHeadHash, sequencer.L2BackupUnsafe().Hash) } @@ -896,7 +895,7 @@ // Create valid TX aliceNonce, err := seqEng.EthClient().PendingNonceAt(t.Ctx(), dp.Addresses.Alice) require.NoError(t, err) data := make([]byte, rand.Intn(100)) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) require.NoError(t, err) baseFee := seqEng.l2Chain.CurrentBlock().BaseFee tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{ @@ -980,7 +979,12 @@ // Start verifier safe sync verifier.ActL1HeadSignal(t) verifier.l2PipelineIdle = false for !verifier.l2PipelineIdle { - verifier.ActL2PipelineStep(t) + // wait for next pending block + verifier.ActL2EventsUntil(t, func(ev rollup.Event) bool { + _, pending := ev.(engine2.PendingSafeUpdateEvent) + _, idle := ev.(derive.DeriverIdleEvent) + return pending || idle + }, 1000, false) if verifier.L2PendingSafe().Number < targetHeadNumber { // If the span batch is not fully processed, the safe head must not advance. require.Equal(t, verifier.L2Safe().Number, uint64(0)) @@ -1027,7 +1031,12 @@ // Start verifier safe sync verifier.ActL1HeadSignal(t) verifier.l2PipelineIdle = false for !verifier.l2PipelineIdle { - verifier.ActL2PipelineStep(t) + // wait for next pending block + verifier.ActL2EventsUntil(t, func(ev rollup.Event) bool { + _, pending := ev.(engine2.PendingSafeUpdateEvent) + _, idle := ev.(derive.DeriverIdleEvent) + return pending || idle + }, 1000, false) if verifier.L2PendingSafe().Number < targetHeadNumber { // If the span batch is not fully processed, the safe head must not advance. require.Equal(t, verifier.L2Safe().Number, uint64(0))
diff --git OP/op-e2e/brotli_batcher_test.go CELO/op-e2e/brotli_batcher_test.go index 97211c471ba05df73b58aa7ea504b30dd3e11b0b..1ebb27efb5ad775b24cf3f566827e6e158df1121 100644 --- OP/op-e2e/brotli_batcher_test.go +++ CELO/op-e2e/brotli_batcher_test.go @@ -85,7 +85,7 @@ receipt := SendL2Tx(t, cfg, l2Seq, ethPrivKey, func(opts *TxOpts) { opts.Value = big.NewInt(1_000_000_000) opts.Nonce = 1 // Already have deposit opts.ToAddr = &common.Address{0xff, 0xff} - opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false) + opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false, nil) require.NoError(t, err) opts.VerifyOnClients(l2Verif) })
diff --git OP/op-e2e/celo/babel.config.cjs CELO/op-e2e/celo/babel.config.cjs new file mode 100644 index 0000000000000000000000000000000000000000..a76dfe63099c9948289ad9c3cad15a8e391e7e76 --- /dev/null +++ CELO/op-e2e/celo/babel.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }]], +}
diff --git OP/op-e2e/celo/foundry.toml CELO/op-e2e/celo/foundry.toml new file mode 100644 index 0000000000000000000000000000000000000000..8df5305625e49af8485993916160a3fe99512d1d --- /dev/null +++ CELO/op-e2e/celo/foundry.toml @@ -0,0 +1,19 @@ +[profile.default] + +# Compilation settings +src = '../../packages/contracts-bedrock/src/celo/' +out = 'forge-artifacts' +remappings = [ + '@openzeppelin/contracts-upgradeable/=../../packages/contracts-bedrock/lib/openzeppelin-contracts-upgradeable/contracts', + '@openzeppelin/contracts/=../../packages/contracts-bedrock/lib/openzeppelin-contracts/contracts', + 'forge-std/=../../packages/contracts-bedrock/lib/forge-std/src', +] +allow_paths = ["../../packages/contracts-bedrock/"] +extra_output = ['abi'] +bytecode_hash = 'none' +evm_version = "cancun" +fs_permissions = [ + { access='read', path='../../packages/contracts-bedrock/' }, +] +libs = ["lib"] +
diff --git OP/op-e2e/celo/jest.config.json CELO/op-e2e/celo/jest.config.json new file mode 100644 index 0000000000000000000000000000000000000000..aea28c9f68597eeaca251715ad80f924c20670e3 --- /dev/null +++ CELO/op-e2e/celo/jest.config.json @@ -0,0 +1,5 @@ +{ + "transformIgnorePatterns": [ + "node_modules/(?!(string-width|strip-ansi|ansi-regex|test-json-import)/)" + ] +}
diff --git OP/op-e2e/celo/package-lock.json CELO/op-e2e/celo/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..b4e15405b5ab96e1edd45a99581f95fb992d3be7 --- /dev/null +++ CELO/op-e2e/celo/package-lock.json @@ -0,0 +1,6543 @@ +{ + "name": "testsuite", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "testsuite", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "reverse-mirage": "^1.1.0", + "viem": "^2.13.1" + }, + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "babel-jest": "^29.7.0", + "jest": "^29.7.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", + "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", + "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.9", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-module-transforms": "^7.24.9", + "@babel/helpers": "^7.24.8", + "@babel/parser": "^7.24.8", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", + "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", + "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz", + "integrity": "sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", + "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", + "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz", + "integrity": "sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.8.tgz", + "integrity": "sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.8", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.8", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", + "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.8", + "@babel/types": "^7.24.8", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", + "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", + "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "20.14.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", + "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/abitype": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.5.tgz", + "integrity": "sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001642", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", + "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/create-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/create-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.829", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz", + "integrity": "sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isows": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.4.tgz", + "integrity": "sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", + "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reverse-mirage": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reverse-mirage/-/reverse-mirage-1.1.0.tgz", + "integrity": "sha512-cA1O7GR0pn4rMFoaiEG7Skms9GenuW91DtCxeR5hphyNhH90eowV4RmUVlVPVS11CPkezm/iUjnCfmxlHri05w==", + "peerDependencies": { + "typescript": ">=5.0.4", + "viem": ">=2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/viem": { + "version": "2.17.4", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.17.4.tgz", + "integrity": "sha512-6gmBB85I7z1qt/+yPPS+i4L2jNPJqCs+SEb+26WnKVYez13svSzjYMsU9OYYlPFpQmpGSy9dV2bKW6VX68FTgg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.0", + "@noble/curves": "1.4.0", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0", + "abitype": "1.0.5", + "isows": "1.0.4", + "ws": "8.17.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +}
diff --git OP/op-e2e/celo/package.json CELO/op-e2e/celo/package.json new file mode 100644 index 0000000000000000000000000000000000000000..0026da775c4f3d242af7554e491c5831303b5303 --- /dev/null +++ CELO/op-e2e/celo/package.json @@ -0,0 +1,22 @@ +{ + "name": "testsuite", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "dist/test.js", + "scripts": { + "test": "jest tests --detectOpenHandles" + }, + "author": "Celo Labs Inc.", + "license": "ISC", + "dependencies": { + "reverse-mirage": "^1.1.0", + "viem": "^2.13.1" + }, + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "babel-jest": "^29.7.0", + "jest": "^29.7.0" + } +}
diff --git OP/op-e2e/celo/run_all_tests.sh CELO/op-e2e/celo/run_all_tests.sh new file mode 100755 index 0000000000000000000000000000000000000000..ecafe64b51fc6603594bd0640d5a71afcdd422fa --- /dev/null +++ CELO/op-e2e/celo/run_all_tests.sh @@ -0,0 +1,56 @@ +#!/bin/bash +#shellcheck disable=SC1091 +set -eo pipefail + +SCRIPT_DIR=$(readlink -f "$(dirname "$0")") +TEST_GLOB=$1 +spawn_devnet=${SPAWN_DEVNET:-true} + +if [[ $spawn_devnet != false ]]; then + ## Start geth + cd "$SCRIPT_DIR/../.." || exit 1 + trap 'cd "$SCRIPT_DIR/../.." && make devnet-down' EXIT # kill bg job at exit + DEVNET_L2OO=true DEVNET_CELO=true make devnet-up +fi + +cd "$SCRIPT_DIR" || exit 1 +source "$SCRIPT_DIR/shared.sh" + +# Wait for geth to be ready +for _ in {1..10}; do + if cast block &>/dev/null; then + echo geth ready + break + fi + sleep 0.2 +done + +## Run tests +echo Start tests +failures=0 +tests=0 +for f in test_*"$TEST_GLOB"*; do + echo -e "\nRun $f" + if "./$f"; then + tput setaf 2 || true + echo "PASS $f" + else + tput setaf 1 || true + echo "FAIL $f ❌" + ((failures++)) || true + fi + tput sgr0 || true + ((tests++)) || true +done + +## Final summary +echo +if [[ $failures -eq 0 ]]; then + tput setaf 2 || true + echo All tests succeeded! +else + tput setaf 1 || true + echo "$failures/$tests" failed. +fi +tput sgr0 || true +exit "$failures"
diff --git OP/op-e2e/celo/shared.sh CELO/op-e2e/celo/shared.sh new file mode 100644 index 0000000000000000000000000000000000000000..92e9be7be28ec1d52cd4b29af0204a1a8fa6791e --- /dev/null +++ CELO/op-e2e/celo/shared.sh @@ -0,0 +1,12 @@ +#!/bin/bash +#shellcheck disable=SC2034 # unused vars make sense in a shared file + +export ETH_RPC_URL=http://localhost:9545 +export ETH_RPC_URL_L1=http://localhost:8545 + +export ACC_PRIVKEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +ACC_ADDR=$(cast wallet address $ACC_PRIVKEY) +export ACC_ADDR +export REGISTRY_ADDR=0x000000000000000000000000000000000000ce10 +export TOKEN_ADDR=0x471ece3750da237f93b8e339c536989b8978a438 +export FEE_CURRENCY_DIRECTORY_ADDR=0x71FFbD48E34bdD5a87c3c683E866dc63b8B2a685
diff --git OP/op-e2e/celo/src/OptimismPortal.js CELO/op-e2e/celo/src/OptimismPortal.js new file mode 100644 index 0000000000000000000000000000000000000000..80b02f38341420bc72c43c39150aaf591dfa09fa --- /dev/null +++ CELO/op-e2e/celo/src/OptimismPortal.js @@ -0,0 +1,658 @@ +export const OptimismPortalABI = [ + { + type: 'constructor', + inputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'receive', + stateMutability: 'payable', + }, + { + type: 'function', + name: 'balance', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'depositERC20Transaction', + inputs: [ + { + name: '_to', + type: 'address', + internalType: 'address', + }, + { + name: '_mint', + type: 'uint256', + internalType: 'uint256', + }, + { + name: '_value', + type: 'uint256', + internalType: 'uint256', + }, + { + name: '_gasLimit', + type: 'uint64', + internalType: 'uint64', + }, + { + name: '_isCreation', + type: 'bool', + internalType: 'bool', + }, + { + name: '_data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'depositTransaction', + inputs: [ + { + name: '_to', + type: 'address', + internalType: 'address', + }, + { + name: '_value', + type: 'uint256', + internalType: 'uint256', + }, + { + name: '_gasLimit', + type: 'uint64', + internalType: 'uint64', + }, + { + name: '_isCreation', + type: 'bool', + internalType: 'bool', + }, + { + name: '_data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'donateETH', + inputs: [], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'finalizeWithdrawalTransaction', + inputs: [ + { + name: '_tx', + type: 'tuple', + internalType: 'struct Types.WithdrawalTransaction', + components: [ + { + name: 'nonce', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'sender', + type: 'address', + internalType: 'address', + }, + { + name: 'target', + type: 'address', + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'gasLimit', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'finalizedWithdrawals', + inputs: [ + { + name: '', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'guardian', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'initialize', + inputs: [ + { + name: '_l2Oracle', + type: 'address', + internalType: 'contract L2OutputOracle', + }, + { + name: '_systemConfig', + type: 'address', + internalType: 'contract SystemConfig', + }, + { + name: '_superchainConfig', + type: 'address', + internalType: 'contract SuperchainConfig', + }, + { + name: '_initialBalance', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'isOutputFinalized', + inputs: [ + { + name: '_l2OutputIndex', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'l2Oracle', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract L2OutputOracle', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'l2Sender', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'minimumGasLimit', + inputs: [ + { + name: '_byteCount', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'uint64', + internalType: 'uint64', + }, + ], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'params', + inputs: [], + outputs: [ + { + name: 'prevBaseFee', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'prevBoughtGas', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'prevBlockNum', + type: 'uint64', + internalType: 'uint64', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'paused', + inputs: [], + outputs: [ + { + name: 'paused_', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'proveWithdrawalTransaction', + inputs: [ + { + name: '_tx', + type: 'tuple', + internalType: 'struct Types.WithdrawalTransaction', + components: [ + { + name: 'nonce', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'sender', + type: 'address', + internalType: 'address', + }, + { + name: 'target', + type: 'address', + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'gasLimit', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: '_l2OutputIndex', + type: 'uint256', + internalType: 'uint256', + }, + { + name: '_outputRootProof', + type: 'tuple', + internalType: 'struct Types.OutputRootProof', + components: [ + { + name: 'version', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'stateRoot', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'messagePasserStorageRoot', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'latestBlockhash', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + }, + { + name: '_withdrawalProof', + type: 'bytes[]', + internalType: 'bytes[]', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'provenWithdrawals', + inputs: [ + { + name: '', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [ + { + name: 'outputRoot', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'timestamp', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'l2OutputIndex', + type: 'uint128', + internalType: 'uint128', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'setGasPayingToken', + inputs: [ + { + name: '_token', + type: 'address', + internalType: 'address', + }, + { + name: '_decimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: '_name', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: '_symbol', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'superchainConfig', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract SuperchainConfig', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'systemConfig', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract SystemConfig', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'version', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'view', + }, + { + type: 'event', + name: 'Initialized', + inputs: [ + { + name: 'version', + type: 'uint8', + indexed: false, + internalType: 'uint8', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TransactionDeposited', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'version', + type: 'uint256', + indexed: true, + internalType: 'uint256', + }, + { + name: 'opaqueData', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'WithdrawalFinalized', + inputs: [ + { + name: 'withdrawalHash', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + { + name: 'success', + type: 'bool', + indexed: false, + internalType: 'bool', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'WithdrawalProven', + inputs: [ + { + name: 'withdrawalHash', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'BadTarget', + inputs: [], + }, + { + type: 'error', + name: 'CallPaused', + inputs: [], + }, + { + type: 'error', + name: 'ContentLengthMismatch', + inputs: [], + }, + { + type: 'error', + name: 'EmptyItem', + inputs: [], + }, + { + type: 'error', + name: 'GasEstimation', + inputs: [], + }, + { + type: 'error', + name: 'InvalidDataRemainder', + inputs: [], + }, + { + type: 'error', + name: 'InvalidHeader', + inputs: [], + }, + { + type: 'error', + name: 'LargeCalldata', + inputs: [], + }, + { + type: 'error', + name: 'NoValue', + inputs: [], + }, + { + type: 'error', + name: 'NonReentrant', + inputs: [], + }, + { + type: 'error', + name: 'OnlyCustomGasToken', + inputs: [], + }, + { + type: 'error', + name: 'OutOfGas', + inputs: [], + }, + { + type: 'error', + name: 'SmallGasLimit', + inputs: [], + }, + { + type: 'error', + name: 'TransferFailed', + inputs: [], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [], + }, + { + type: 'error', + name: 'UnexpectedList', + inputs: [], + }, + { + type: 'error', + name: 'UnexpectedString', + inputs: [], + }, +]
diff --git OP/op-e2e/celo/src/chain.js CELO/op-e2e/celo/src/chain.js new file mode 100644 index 0000000000000000000000000000000000000000..6d5ec9e73ea594fb39c1cd43a53705040db903f6 --- /dev/null +++ CELO/op-e2e/celo/src/chain.js @@ -0,0 +1,66 @@ +import { chainConfig } from 'viem/op-stack' +import { defineChain } from 'viem' + +export function makeChainConfigs(l1ChainID, l2ChainID, contractAddresses) { + console.log(process.env) + return { + l2: defineChain({ + formatters: { + ...chainConfig.formatters, + }, + serializers: { + ...chainConfig.serializers, + }, + id: l2ChainID, + name: 'Celo', + nativeCurrency: { + decimals: 18, + name: 'Celo - native currency', + symbol: 'CELO', + }, + rpcUrls: { + default: { + http: [process.env.ETH_RPC_URL], + }, + }, + contracts: { + ...chainConfig.contracts, + l2OutputOracle: { + [l1ChainID]: { + address: contractAddresses.L2OutputOracleProxy, + }, + }, + portal: { + [l1ChainID]: { + address: contractAddresses.OptimismPortalProxy, + }, + }, + l1StandardBridge: { + [l1ChainID]: { + address: contractAddresses.L1StandardBridgeProxy, + }, + }, + }, + }), + l1: defineChain({ + id: l1ChainID, + testnet: true, + name: 'Ethereum L1', + nativeCurrency: { + decimals: 18, + name: 'Ether', + symbol: 'ETH', + }, + rpcUrls: { + default: { + http: [process.env.ETH_RPC_URL_L1], + }, + }, + contracts: { + multicall3: { + address: contractAddresses.Multicall3, + }, + }, + }), + } +}
diff --git OP/op-e2e/celo/src/config.js CELO/op-e2e/celo/src/config.js new file mode 100644 index 0000000000000000000000000000000000000000..7e410dffbdd0de20f4dc8b781d6aabcd21f7448f --- /dev/null +++ CELO/op-e2e/celo/src/config.js @@ -0,0 +1,98 @@ +import { createPublicClient, createWalletClient, http } from 'viem' +import { readContract } from 'viem/actions' +import { constructDepositCustomGas } from './deposit.js' +import { + getERC20, + simulateERC20Transfer, + getERC20BalanceOf, + getERC20Symbol, + getERC20Decimals, + simulateERC20Approve, +} from 'reverse-mirage' +import { + publicActionsL1, + publicActionsL2, + walletActionsL1, + walletActionsL2, +} from 'viem/op-stack' + +export function makeReadContract(contractAddress, contractABI) { + return (client) => { + return { + readContract: (args) => { + const rcArgs = { + address: contractAddress, + abi: contractABI, + functionName: args.functionName, + args: args.args, + } + return readContract(client, rcArgs) + }, + } + } +} + +export function erc20PublicActions(client) { + return { + getERC20: (args) => getERC20(client, args), + getERC20Symbol: (args) => getERC20Symbol(client, args), + getERC20BalanceOf: (args) => getERC20BalanceOf(client, args), + getERC20Decimals: (args) => getERC20Decimals(client, args), + } +} +export function erc20WalletActions(client) { + return { + simulateERC20Transfer: (args) => { + return simulateERC20Transfer(client, { args: args }) + }, + simulateERC20Approve: (args) => { + return simulateERC20Approve(client, { args: args }) + }, + } +} + +export function celoL1PublicActions(client) { + return { + prepareDepositGasPayingTokenERC20: (args) => { + return constructDepositCustomGas(client, args) + }, + } +} + +export function setupClients(l1ChainConfig, l2ChainConfig, account) { + return { + l1: { + public: createPublicClient({ + account, + chain: l1ChainConfig, + transport: http(), + }) + .extend(publicActionsL1()) + .extend(celoL1PublicActions) + .extend(erc20PublicActions), + wallet: createWalletClient({ + account, + chain: l1ChainConfig, + transport: http(), + }) + .extend(erc20WalletActions) + .extend(walletActionsL1()), + }, + l2: { + public: createPublicClient({ + account, + chain: l2ChainConfig, + transport: http(), + }) + .extend(publicActionsL2()) + .extend(erc20PublicActions), + wallet: createWalletClient({ + account, + chain: l2ChainConfig, + transport: http(), + }) + .extend(erc20WalletActions) + .extend(walletActionsL2()), + }, + } +}
diff --git OP/op-e2e/celo/src/deposit.js CELO/op-e2e/celo/src/deposit.js new file mode 100644 index 0000000000000000000000000000000000000000..2e1f5ef17dbdf0eadd4fba293eba79712a1fd0b2 --- /dev/null +++ CELO/op-e2e/celo/src/deposit.js @@ -0,0 +1,127 @@ +import { getL2TransactionHashes } from 'viem/op-stack' +import { OptimismPortalABI } from './OptimismPortal.js' + +// public client functionality +export async function constructDepositCustomGas(client, parameters) { + const { + account, + chain = client.chain, + gas, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + request: { + data = '0x', + gas: l2Gas, + isCreation = false, + mint, + to = '0x', + value, + }, + targetChain, + } = parameters + + const portalAddress = (() => { + if (parameters.portalAddress) return parameters.portalAddress + if (chain) return targetChain.contracts.portal[chain.id].address + return Object.values(targetChain.contracts.portal)[0].address + })() + const callArgs = { + account: account, + abi: OptimismPortalABI, + address: portalAddress, + chain, + functionName: 'depositERC20Transaction', + /// @notice Entrypoint to depositing an ERC20 token as a custom gas token. + /// This function depends on a well formed ERC20 token. There are only + /// so many checks that can be done on chain for this so it is assumed + /// that chain operators will deploy chains with well formed ERC20 tokens. + /// @param _to Target address on L2. + /// @param _mint Units of ERC20 token to deposit into L2. + /// @param _value Units of ERC20 token to send on L2 to the recipient. + /// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1. + /// @param _isCreation Whether or not the transaction is a contract creation. + /// @param _data Data to trigger the recipient with. + args: [ + isCreation ? zeroAddress : to, + mint ?? value ?? 0n, + value ?? mint ?? 0n, + l2Gas, + isCreation, + data, + ], + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + } + const gas_ = + typeof gas !== 'number' && gas !== null + ? await client.estimateContractGas(callArgs) + : undefined + callArgs.gas = gas_ + const result = client.simulateContract(callArgs) + return { result: result, args: callArgs } +} + +export async function deposit(args, config) { + var spentGas = BigInt(0) + const depositArgs = await config.client.l2.public.buildDepositTransaction({ + mint: args.mint, + to: args.to, + }) + + const celoToken = await config.client.l1.public.getERC20({ + erc20: { + address: config.addresses.CustomGasTokenProxy, + chainID: config.client.l1.public.chain.id, + }, + }) + const portalAddress = + config.client.l2.public.chain.contracts.portal[ + config.client.l1.public.chain.id + ].address + const approve = await config.client.l1.wallet.simulateERC20Approve({ + amount: { amount: args.mint, token: celoToken }, + spender: portalAddress, + }) + if (!approve.result) { + return { + success: false, + l1GasPayment: spentGas, + } + } + + const approveHash = await config.client.l1.wallet.writeContract( + approve.request + ) + // Wait for the L1 transaction to be processed. + const approveReceipt = + await config.client.l1.public.waitForTransactionReceipt({ + hash: approveHash, + }) + + spentGas += approveReceipt.gasUsed * approveReceipt.effectiveGasPrice + const dep = + await config.client.l1.public.prepareDepositGasPayingTokenERC20(depositArgs) + const hash = await config.client.l1.wallet.writeContract(dep.args) + + // Wait for the L1 transaction to be processed. + const receipt = await config.client.l1.public.waitForTransactionReceipt({ + hash: hash, + }) + + spentGas += receipt.gasUsed * receipt.effectiveGasPrice + + // Get the L2 transaction hash from the L1 transaction receipt. + const [l2Hash] = getL2TransactionHashes(receipt) + + // Wait for the L2 transaction to be processed. + const l2Receipt = await config.client.l2.public.waitForTransactionReceipt({ + hash: l2Hash, + }) + + return { + success: l2Receipt.status == 'success', + l1GasPayment: spentGas, + } +}
diff --git OP/op-e2e/celo/src/withdraw.js CELO/op-e2e/celo/src/withdraw.js new file mode 100644 index 0000000000000000000000000000000000000000..b52740e75761056942dfe40386334c40e3bd8123 --- /dev/null +++ CELO/op-e2e/celo/src/withdraw.js @@ -0,0 +1,63 @@ +export const withdraw = async function (args, config) { + const initiateHash = await config.client.l2.wallet.initiateWithdrawal({ + request: { + gas: args.gas, + to: args.to, + value: args.amount, + }, + }) + const receipt = await config.client.l2.public.waitForTransactionReceipt({ + hash: initiateHash, + }) + + const l2GasPayment = + receipt.gasUsed * receipt.effectiveGasPrice + receipt.l1Fee + + // FIXME: this blocks longer, the longer the devnet is running, see + // https://github.com/ethereum-optimism/optimism/issues/7668 + // NOTE: this function requires the mulitcall contract to be deployed + // on the L1 chain. + const { output, withdrawal } = await config.client.l1.public.waitToProve({ + receipt, + targetChain: config.client.l2.public.chain, + }) + // + + const proveWithdrawalArgs = + await config.client.l2.public.buildProveWithdrawal({ + output, + withdrawal, + }) + const proveHash = + await config.client.l1.wallet.proveWithdrawal(proveWithdrawalArgs) + + const proveReceipt = await config.client.l1.public.waitForTransactionReceipt({ + hash: proveHash, + }) + if (proveReceipt.status != 'success') { + return { + success: false, + l2GasPayment: l2GasPayment, + } + } + + await config.client.l1.public.waitToFinalize({ + withdrawalHash: withdrawal.withdrawalHash, + targetChain: config.client.l2.public.chain, + }) + + const finalizeHash = await config.client.l1.wallet.finalizeWithdrawal({ + targetChain: config.client.l2.public.chain, + withdrawal, + }) + + const finalizeReceipt = + await config.client.l1.public.waitForTransactionReceipt({ + hash: finalizeHash, + }) + + return { + success: finalizeReceipt.status == 'success', + l2GasPayment: l2GasPayment, + } +}
diff --git OP/op-e2e/celo/test_npm.sh CELO/op-e2e/celo/test_npm.sh new file mode 100755 index 0000000000000000000000000000000000000000..89783597300cf124d3091a535f3e01473998ae61 --- /dev/null +++ CELO/op-e2e/celo/test_npm.sh @@ -0,0 +1,6 @@ +#!/bin/bash +#shellcheck disable=SC1091 +set -eo pipefail + +source shared.sh +npm test
diff --git OP/op-e2e/celo/test_weth_bridge.sh CELO/op-e2e/celo/test_weth_bridge.sh new file mode 100755 index 0000000000000000000000000000000000000000..ce77a5a308b6d1c6a579d7a503ce385ddf48dab0 --- /dev/null +++ CELO/op-e2e/celo/test_weth_bridge.sh @@ -0,0 +1,42 @@ +#!/bin/bash +#shellcheck disable=SC2086,SC1091 +set -eo pipefail +set -x + +source shared.sh +SCRIPT_DIR=$(readlink -f "$(dirname "$0")") +CONTRACTS_DIR=$SCRIPT_DIR/../../packages/contracts-bedrock + +# Deploy WETH +L1_WETH=$( + ETH_RPC_URL=$ETH_RPC_URL_L1 forge create --private-key=$ACC_PRIVKEY --root $CONTRACTS_DIR $CONTRACTS_DIR/src/dispute/weth/WETH98.sol:WETH98 --json | jq .deployedTo -r +) + +# create ERC20 token on L2 +L2_TOKEN=$( + cast send --private-key $ACC_PRIVKEY 0x4200000000000000000000000000000000000012 "createOptimismMintableERC20(address,string,string)" $L1_WETH "Wrapped Ether" "WETH" --json \ + | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address +) + +# Wrap some ETH +ETH_RPC_URL=$ETH_RPC_URL_L1 cast send --private-key $ACC_PRIVKEY $L1_WETH --value 1ether +# Approve transfer to bridge +L1_BRIDGE_ADDR=$(cast call 0x4200000000000000000000000000000000000010 'otherBridge() returns (address)') +ETH_RPC_URL=$ETH_RPC_URL_L1 cast send --private-key $ACC_PRIVKEY $L1_WETH 'approve(address, uint256) returns (bool)' $L1_BRIDGE_ADDR 1ether +# Bridge to L2 +ETH_RPC_URL=$ETH_RPC_URL_L1 cast send --private-key $ACC_PRIVKEY $L1_BRIDGE_ADDR 'bridgeERC20(address _localToken, address _remoteToken, uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData)' $L1_WETH $L2_TOKEN 0.3ether 50000 0x --gas-limit 6000000 + +# Setup up oracle and FeeCurrencyDirectory +ORACLE=$(forge create --private-key=$ACC_PRIVKEY --root $CONTRACTS_DIR $CONTRACTS_DIR/src/celo/testing/MockSortedOracles.sol:MockSortedOracles --json | jq .deployedTo -r) +cast send --private-key $ACC_PRIVKEY $ORACLE 'setMedianRate(address, uint256)' $L2_TOKEN 100000000000000000 +cast send --private-key $ACC_PRIVKEY $FEE_CURRENCY_DIRECTORY_ADDR 'setCurrencyConfig(address, address, uint256)' $L2_TOKEN $ORACLE 60000 + +# Check balance from bridging (we intentionally don't do this right after bridging, since it takes a bit) +L2_BALANCE=$(cast call $L2_TOKEN 'balanceOf(address) returns (uint256)' $ACC_ADDR) +echo L2 balance: $L2_BALANCE +[[ $(echo $L2_BALANCE | awk '{print $1}') -gt 0 ]] || (echo "Bridging to L2 failed!"; exit 1) + +# Send fee currency tx! +#TXHASH=$(~/op-geth/e2e_test/js-tests/send_tx.mjs 901 $ACC_PRIVKEY $L2_TOKEN) +#cast receipt $TXHASH +echo You can use privkey $ACC_PRIVKEY to pay for txs with $L2_TOKEN, now.
diff --git OP/op-e2e/celo/tests/setup.js CELO/op-e2e/celo/tests/setup.js new file mode 100644 index 0000000000000000000000000000000000000000..585c9ccdbed1a3b673968e484ba44dd82abd6507 --- /dev/null +++ CELO/op-e2e/celo/tests/setup.js @@ -0,0 +1,64 @@ +import { setupClients } from '../src/config.js' +import { makeChainConfigs } from '../src/chain.js' +import { privateKeyToAccount } from 'viem/accounts' +import { readFileSync } from 'fs' + +// Default Anvil dev account that has a pre-allocation on the op-devnet: +// "test test test test test test test test test test test junk" mnemonic account, +// on path "m/44'/60'/0'/0/6". +// Address: 0x976EA74026E726554dB657fA54763abd0C3a0aa9. +const privKey = + '0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e' + +async function waitForNoError(func, timeout) { + const start = Date.now() + while (Date.now() - start < timeout) { + try { + await func() + return true + } catch (error) { } + await new Promise((r) => setTimeout(r, 1000)) + } + return false +} + +async function waitReachable(client, timeout) { + const f = async () => client.getChainId() + return waitForNoError(f, timeout) +} + +async function waitForNextL2Output(client, l2ChainConfig, timeout) { + const f = async () => + client.waitForNextL2Output({ + pollingInterval: 500, + l2BlockNumber: 0, + targetChain: l2ChainConfig, + }) + return waitForNoError(f, timeout) +} + +export async function setup() { + const contractAddrs = JSON.parse( + readFileSync('../../.devnet/addresses.json', 'utf8') + ) + const config = { account: privateKeyToAccount(privKey) } + const chainConfig = makeChainConfigs(900, 901, contractAddrs) + + config.client = setupClients( + chainConfig.l1, + chainConfig.l2, + config.account, + contractAddrs + ) + config.addresses = contractAddrs + + const success = await Promise.all([ + waitReachable(config.client.l1.public, 10_000), + waitReachable(config.client.l2.public, 10_000), + waitForNextL2Output(config.client.l1.public, chainConfig.l2, 60_000), + ]) + if (success.every((v) => v == true)) { + return config + } + throw new Error('l1 and l2 clients not reachable within the deadline') +}
diff --git OP/op-e2e/celo/tests/tokenduality.test.js CELO/op-e2e/celo/tests/tokenduality.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9980c81fece42a92f061e53be6cec62627a944f2 --- /dev/null +++ CELO/op-e2e/celo/tests/tokenduality.test.js @@ -0,0 +1,42 @@ +import { createAmountFromString } from 'reverse-mirage' +import { setup } from './setup.js' + +const minute = 60 * 1000 +let config = {} + +beforeAll(async () => { + config = await setup() +}, 30_000) + +test( + 'test token duality', + async () => { + const receiverAddr = '0x000000000000000000000000000000000000dEaD' + const dualityToken = await config.client.l2.public.getERC20({ + erc20: { + address: '0x471ece3750da237f93b8e339c536989b8978a438', + chainID: config.client.l2.public.chain.id, + }, + }) + const balanceBefore = await config.client.l2.public.getBalance({ + address: receiverAddr, + }) + + const sendAmount = createAmountFromString(dualityToken, '100') + const { request } = await config.client.l2.wallet.simulateERC20Transfer({ + to: receiverAddr, + amount: sendAmount, + }) + const transferHash = await config.client.l2.wallet.writeContract(request) + const receipt = await config.client.l2.public.waitForTransactionReceipt({ + hash: transferHash, + }) + expect(receipt.status).toBe('success') + const balanceAfter = await config.client.l2.public.getBalance({ + address: receiverAddr, + }) + + expect(balanceAfter).toBe(balanceBefore + sendAmount.amount) + }, + 1 * minute +)
diff --git OP/op-e2e/celo/tests/withdraw_deposit.test.js CELO/op-e2e/celo/tests/withdraw_deposit.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b7235239f4d1ff2653420f7406c322cbd4ceb83e --- /dev/null +++ CELO/op-e2e/celo/tests/withdraw_deposit.test.js @@ -0,0 +1,77 @@ +import { withdraw } from '../src/withdraw.js' +import { deposit } from '../src/deposit.js' +import { parseEther } from 'viem' +import { setup } from './setup.js' + +const minute = 60 * 1000 +var config = {} + +beforeAll(async () => { + config = await setup() +}, minute) + +test( + 'execute a withdraw and a deposit in succession', + async () => { + const celoToken = await config.client.l1.public.getERC20({ + erc20: { + address: config.addresses.CustomGasTokenProxy, + chainID: config.client.l1.public.chain.id, + }, + }) + const balanceL1Before = await config.client.l1.public.getERC20BalanceOf({ + erc20: celoToken, + address: config.account.address, + }) + const balanceL2Before = await config.client.l2.public.getBalance({ + address: config.account.address, + }) + const withdrawAmount = parseEther('1') + const withdrawResult = await withdraw( + { + amount: withdrawAmount, + to: config.account.address, + gas: 21_000n, + }, + config + ) + expect(withdrawResult.success).toBe(true) + const balanceL1AfterWithdraw = + await config.client.l1.public.getERC20BalanceOf({ + erc20: celoToken, + address: config.account.address, + }) + const balanceL2AfterWithdraw = await config.client.l2.public.getBalance({ + address: config.account.address, + }) + expect(balanceL1AfterWithdraw.amount).toBe( + balanceL1Before.amount + BigInt(withdrawAmount) + ) + expect(balanceL2AfterWithdraw).toBe( + balanceL2Before - BigInt(withdrawAmount) - withdrawResult.l2GasPayment + ) + const depositResult = await deposit( + { + mint: withdrawAmount, + to: config.account.address, + }, + config + ) + expect(depositResult.success).toBe(true) + + const balanceL1AfterDeposit = + await config.client.l1.public.getERC20BalanceOf({ + erc20: celoToken, + address: config.account.address, + }) + const balanceL2AfterDeposit = await config.client.l2.public.getBalance({ + address: config.account.address, + }) + + expect(balanceL1AfterDeposit.amount).toBe(balanceL1Before.amount) + expect(balanceL2AfterDeposit).toBe( + balanceL2Before - withdrawResult.l2GasPayment + ) + }, + 15 * minute +)
diff --git OP/op-e2e/config/init.go CELO/op-e2e/config/init.go index d53fd0697124144db2ca775e64958acf7044f49d..c5a38ad5a6af46394a0793f4ec958750bb7fa14f 100644 --- OP/op-e2e/config/init.go +++ CELO/op-e2e/config/init.go @@ -15,6 +15,7 @@ "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log"   + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-e2e/external" op_service "github.com/ethereum-optimism/optimism/op-service" @@ -39,12 +40,12 @@ // foundry deploy script. These are globally exported to be used // in end to end tests.   // L1Allocs represents the L1 genesis block state. - L1Allocs *genesis.ForgeAllocs + L1Allocs *foundry.ForgeAllocs // L1Deployments maps contract names to accounts in the L1 // genesis block state. L1Deployments *genesis.L1Deployments // l2Allocs represents the L2 allocs, by hardfork/mode (e.g. delta, ecotone, interop, other) - l2Allocs map[genesis.L2AllocsMode]*genesis.ForgeAllocs + l2Allocs map[genesis.L2AllocsMode]*foundry.ForgeAllocs // DeployConfig represents the deploy config used by the system. DeployConfig *genesis.DeployConfig // ExternalL2Shim is the shim to use if external ethereum client testing is @@ -107,14 +108,14 @@ if err := allExist(l1AllocsPath, l1DeploymentsPath, deployConfigPath); err != nil { return }   - L1Allocs, err = genesis.LoadForgeAllocs(l1AllocsPath) + L1Allocs, err = foundry.LoadForgeAllocs(l1AllocsPath) if err != nil { panic(err) } - l2Allocs = make(map[genesis.L2AllocsMode]*genesis.ForgeAllocs) + l2Allocs = make(map[genesis.L2AllocsMode]*foundry.ForgeAllocs) mustL2Allocs := func(mode genesis.L2AllocsMode) { name := "allocs-l2-" + string(mode) - allocs, err := genesis.LoadForgeAllocs(filepath.Join(l2AllocsDir, name+".json")) + allocs, err := foundry.LoadForgeAllocs(filepath.Join(l2AllocsDir, name+".json")) if err != nil { panic(err) } @@ -153,7 +154,7 @@ } } }   -func L2Allocs(mode genesis.L2AllocsMode) *genesis.ForgeAllocs { +func L2Allocs(mode genesis.L2AllocsMode) *foundry.ForgeAllocs { allocs, ok := l2Allocs[mode] if !ok { panic(fmt.Errorf("unknown L2 allocs mode: %q", mode))
diff --git OP/op-e2e/e2eutils/challenger/helper.go CELO/op-e2e/e2eutils/challenger/helper.go index 95b963666d633f12565399be771ff5197381d6c0..5431efbd3992b375fa65a62bb29af01cd9b54448 100644 --- OP/op-e2e/e2eutils/challenger/helper.go +++ CELO/op-e2e/e2eutils/challenger/helper.go @@ -102,22 +102,22 @@ func applyCannonConfig(c *config.Config, t *testing.T, rollupCfg *rollup.Config, l2Genesis *core.Genesis) { require := require.New(t) root := FindMonorepoRoot(t) - c.CannonBin = root + "cannon/bin/cannon" - c.CannonServer = root + "op-program/bin/op-program" + c.Cannon.VmBin = root + "cannon/bin/cannon" + c.Cannon.Server = root + "op-program/bin/op-program" c.CannonAbsolutePreState = root + "op-program/bin/prestate.json" - c.CannonSnapshotFreq = 10_000_000 + c.Cannon.SnapshotFreq = 10_000_000   genesisBytes, err := json.Marshal(l2Genesis) require.NoError(err, "marshall l2 genesis config") genesisFile := filepath.Join(c.Datadir, "l2-genesis.json") require.NoError(os.WriteFile(genesisFile, genesisBytes, 0o644)) - c.CannonL2GenesisPath = genesisFile + c.Cannon.L2GenesisPath = genesisFile   rollupBytes, err := json.Marshal(rollupCfg) require.NoError(err, "marshall rollup config") rollupFile := filepath.Join(c.Datadir, "rollup.json") require.NoError(os.WriteFile(rollupFile, rollupBytes, 0o644)) - c.CannonRollupConfigPath = rollupFile + c.Cannon.RollupConfigPath = rollupFile }   func WithCannon(t *testing.T, rollupCfg *rollup.Config, l2Genesis *core.Genesis) Option { @@ -177,12 +177,12 @@ } require.NotEmpty(t, cfg.TxMgrConfig.PrivateKey, "Missing private key for TxMgrConfig") require.NoError(t, cfg.Check(), "op-challenger config should be valid")   - if cfg.CannonBin != "" { - _, err := os.Stat(cfg.CannonBin) + if cfg.Cannon.VmBin != "" { + _, err := os.Stat(cfg.Cannon.VmBin) require.NoError(t, err, "cannon should be built. Make sure you've run make cannon-prestate") } - if cfg.CannonServer != "" { - _, err := os.Stat(cfg.CannonServer) + if cfg.Cannon.Server != "" { + _, err := os.Stat(cfg.Cannon.Server) require.NoError(t, err, "op-program should be built. Make sure you've run make cannon-prestate") } if cfg.CannonAbsolutePreState != "" {
diff --git OP/op-e2e/e2eutils/disputegame/helper.go CELO/op-e2e/e2eutils/disputegame/helper.go index 6920a6737c10d58e5c6c99815fadc67de96c2b42..0ed6a6a6db035b049f91e93064e75f8b60e76d58 100644 --- OP/op-e2e/e2eutils/disputegame/helper.go +++ CELO/op-e2e/e2eutils/disputegame/helper.go @@ -124,15 +124,14 @@ func (h *FactoryHelper) PreimageHelper(ctx context.Context) *preimage.Helper { opts := &bind.CallOpts{Context: ctx} gameAddr, err := h.Factory.GameImpls(opts, cannonGameType) h.Require.NoError(err) - game, err := bindings.NewFaultDisputeGameCaller(gameAddr, h.Client) + caller := batching.NewMultiCaller(h.Client.Client(), batching.DefaultBatchSize) + game, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, gameAddr, caller) h.Require.NoError(err) - vmAddr, err := game.Vm(opts) + vm, err := game.Vm(ctx) h.Require.NoError(err) - vm, err := bindings.NewMIPSCaller(vmAddr, h.Client) + oracle, err := vm.Oracle(ctx) h.Require.NoError(err) - oracleAddr, err := vm.Oracle(opts) - h.Require.NoError(err) - return preimage.NewHelper(h.T, h.Opts, h.Client, oracleAddr) + return preimage.NewHelper(h.T, h.PrivKey, h.Client, oracle) }   func NewGameCfg(opts ...GameOpt) *GameCfg {
diff --git OP/op-e2e/e2eutils/disputegame/output_cannon_helper.go CELO/op-e2e/e2eutils/disputegame/output_cannon_helper.go index 6b6e6ff4ff7606a20dcda640efdb84d339aa37f9..e2f72c4915d96caa88693803fae92fe4ffcbefde 100644 --- OP/op-e2e/e2eutils/disputegame/output_cannon_helper.go +++ CELO/op-e2e/e2eutils/disputegame/output_cannon_helper.go @@ -62,7 +62,7 @@ rollupClient := g.System.RollupClient(l2Node) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) l1Head := g.GetL1Head(ctx) accessor, err := outputs.NewOutputCannonTraceAccessor( - logger, metrics.NoopMetrics, cfg, l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, prestateBlock, poststateBlock) + logger, metrics.NoopMetrics, cfg.Cannon, l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, prestateBlock, poststateBlock) g.Require.NoError(err, "Failed to create output cannon trace accessor") return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, g.Game, accessor) }
diff --git OP/op-e2e/e2eutils/disputegame/output_game_helper.go CELO/op-e2e/e2eutils/disputegame/output_game_helper.go index f57b92a4508a82402342ce9fbc30cc25df524be9..e7b9a35d10b5618544fa3f3e2846c1d16a4ed4ec 100644 --- OP/op-e2e/e2eutils/disputegame/output_game_helper.go +++ CELO/op-e2e/e2eutils/disputegame/output_game_helper.go @@ -3,7 +3,6 @@ import ( "context" "crypto/ecdsa" - "errors" "fmt" "math/big" "testing" @@ -19,6 +18,7 @@ "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" preimage "github.com/ethereum-optimism/optimism/op-preimage" + "github.com/ethereum-optimism/optimism/op-service/errutil" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -578,20 +578,15 @@ } return false }   -type ErrWithData interface { - ErrorData() interface{} -} - // StepFails attempts to call step and verifies that it fails with ValidStep() func (g *OutputGameHelper) StepFails(ctx context.Context, claimIdx int64, isAttack bool, stateData []byte, proof []byte) { g.T.Logf("Attempting step against claim %v isAttack: %v", claimIdx, isAttack) candidate, err := g.Game.StepTx(uint64(claimIdx), isAttack, stateData, proof) g.Require.NoError(err, "Failed to create tx candidate") _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey, transactions.WithReceiptFail()) - var errData ErrWithData - ok := errors.As(err, &errData) - g.Require.Truef(ok, "Error should provide ErrorData method: %v", err) - g.Require.Equal("0xfb4e40dd", errData.ErrorData(), "Revert reason should be abi encoded ValidStep()") + err = errutil.TryAddRevertReason(err) + g.Require.Error(err, "Transaction should fail") + g.Require.Contains(err.Error(), "0xfb4e40dd", "Revert reason should be abi encoded ValidStep()") }   // ResolveClaim resolves a single subgame
diff --git OP/op-e2e/e2eutils/disputegame/preimage/preimage_helper.go CELO/op-e2e/e2eutils/disputegame/preimage/preimage_helper.go index ad043d3e37a9ebd95f77cc43f6d0e081c6877209..e2eb49836865eaee24c354f55cae3688beaf308d 100644 --- OP/op-e2e/e2eutils/disputegame/preimage/preimage_helper.go +++ CELO/op-e2e/e2eutils/disputegame/preimage/preimage_helper.go @@ -3,6 +3,7 @@ import ( "bytes" "context" + "crypto/ecdsa" "errors" "io" "math/big" @@ -15,13 +16,12 @@ "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" - "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/testutils" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/require" ) @@ -29,28 +29,21 @@ const MinPreimageSize = 10000   type Helper struct { - t *testing.T - require *require.Assertions - client *ethclient.Client - opts *bind.TransactOpts - oracleBindings *bindings.PreimageOracle - oracle *contracts.PreimageOracleContract - uuidProvider atomic.Int64 + t *testing.T + require *require.Assertions + client *ethclient.Client + privKey *ecdsa.PrivateKey + oracle *contracts.PreimageOracleContract + uuidProvider atomic.Int64 }   -func NewHelper(t *testing.T, opts *bind.TransactOpts, client *ethclient.Client, addr common.Address) *Helper { - require := require.New(t) - oracleBindings, err := bindings.NewPreimageOracle(addr, client) - require.NoError(err) - - oracle := contracts.NewPreimageOracleContract(addr, batching.NewMultiCaller(client.Client(), batching.DefaultBatchSize)) +func NewHelper(t *testing.T, privKey *ecdsa.PrivateKey, client *ethclient.Client, oracle *contracts.PreimageOracleContract) *Helper { return &Helper{ - t: t, - require: require, - client: client, - opts: opts, - oracleBindings: oracleBindings, - oracle: oracle, + t: t, + require: require.New(t), + client: client, + privKey: privKey, + oracle: oracle, } }   @@ -82,14 +75,9 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifiers ...InputModifier) types.LargePreimageIdent { data := testutils.RandomData(rand.New(rand.NewSource(1234)), dataSize) s := matrix.NewStateMatrix() uuid := big.NewInt(h.uuidProvider.Add(1)) - bondValue, err := h.oracleBindings.MINBONDSIZE(&bind.CallOpts{}) - h.require.NoError(err) - h.opts.Value = bondValue - tx, err := h.oracleBindings.InitLPP(h.opts, uuid, 32, uint32(len(data))) - h.require.NoError(err) - _, err = wait.ForReceiptOK(ctx, h.client, tx.Hash()) + candidate, err := h.oracle.InitLargePreimage(uuid, 32, uint32(len(data))) h.require.NoError(err) - h.opts.Value = big.NewInt(0) + transactions.RequireSendTx(h.t, ctx, h.client, candidate, h.privKey)   startBlock := big.NewInt(0) totalBlocks := len(data) / types.BlockSize @@ -102,15 +90,10 @@ } for _, modifier := range modifiers { modifier(startBlock.Uint64(), &inputData) } - commitments := make([][32]byte, len(inputData.Commitments)) - for i, commitment := range inputData.Commitments { - commitments[i] = commitment - } - h.t.Logf("Uploading %v parts of preimage %v starting at block %v of about %v Finalize: %v", len(commitments), uuid.Uint64(), startBlock.Uint64(), totalBlocks, inputData.Finalize) - tx, err := h.oracleBindings.AddLeavesLPP(h.opts, uuid, startBlock, inputData.Input, commitments, inputData.Finalize) + h.t.Logf("Uploading %v parts of preimage %v starting at block %v of about %v Finalize: %v", len(inputData.Commitments), uuid.Uint64(), startBlock.Uint64(), totalBlocks, inputData.Finalize) + tx, err := h.oracle.AddLeaves(uuid, startBlock, inputData.Input, inputData.Commitments, inputData.Finalize) h.require.NoError(err) - _, err = wait.ForReceiptOK(ctx, h.client, tx.Hash()) - h.require.NoError(err) + transactions.RequireSendTx(h.t, ctx, h.client, tx, h.privKey) startBlock = new(big.Int).Add(startBlock, big.NewInt(int64(len(inputData.Commitments)))) if inputData.Finalize { break @@ -118,7 +101,7 @@ } }   return types.LargePreimageIdent{ - Claimant: h.opts.From, + Claimant: crypto.PubkeyToAddress(h.privKey.PublicKey), UUID: uuid, } }
diff --git OP/op-e2e/e2eutils/transactions/send.go CELO/op-e2e/e2eutils/transactions/send.go index 09bc029daddc5e41845063aafb7294b1d536cdd7..d154a97515986d923995c75220502986486a5c47 100644 --- OP/op-e2e/e2eutils/transactions/send.go +++ CELO/op-e2e/e2eutils/transactions/send.go @@ -3,12 +3,12 @@ import ( "context" "crypto/ecdsa" - "errors" "fmt" "math/big" "testing"   "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-service/errutil" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/core/types" @@ -20,10 +20,6 @@ )   type SendTxOpt func(cfg *sendTxCfg)   -type ErrWithData interface { - ErrorData() interface{} -} - type sendTxCfg struct { receiptStatus uint64 } @@ -44,9 +40,10 @@ cfg.receiptStatus = types.ReceiptStatusFailed } }   -func RequireSendTx(t *testing.T, ctx context.Context, client *ethclient.Client, candidate txmgr.TxCandidate, privKey *ecdsa.PrivateKey, opts ...SendTxOpt) { - _, _, err := SendTx(ctx, client, candidate, privKey, opts...) +func RequireSendTx(t *testing.T, ctx context.Context, client *ethclient.Client, candidate txmgr.TxCandidate, privKey *ecdsa.PrivateKey, opts ...SendTxOpt) (*types.Transaction, *types.Receipt) { + tx, rcpt, err := SendTx(ctx, client, candidate, privKey, opts...) require.NoError(t, err, "Failed to send transaction") + return tx, rcpt }   func SendTx(ctx context.Context, client *ethclient.Client, candidate txmgr.TxCandidate, privKey *ecdsa.PrivateKey, opts ...SendTxOpt) (*types.Transaction, *types.Receipt, error) { @@ -82,11 +79,7 @@ Data: candidate.TxData, } gas, err := client.EstimateGas(ctx, msg) if err != nil { - var errWithData ErrWithData - if errors.As(err, &errWithData) { - return nil, nil, fmt.Errorf("failed to estimate gas. errdata: %v err: %w", errWithData.ErrorData(), err) - } - return nil, nil, fmt.Errorf("failed to estimate gas: %w", err) + return nil, nil, fmt.Errorf("failed to estimate gas: %w", errutil.TryAddRevertReason(err)) }   tx := types.MustSignNewTx(privKey, types.LatestSignerForChainID(chainID), &types.DynamicFeeTx{ @@ -101,7 +94,7 @@ Gas: gas, }) err = client.SendTransaction(ctx, tx) if err != nil { - return nil, nil, fmt.Errorf("failed to send transaction: %w", err) + return nil, nil, fmt.Errorf("failed to send transaction: %w", errutil.TryAddRevertReason(err)) } receipt, err := wait.ForReceipt(ctx, client, tx.Hash(), cfg.receiptStatus) if err != nil {
diff --git OP/op-e2e/eip4844_test.go CELO/op-e2e/eip4844_test.go index 5b5cc1d5308edf3d0f2fa927da4838c169128225..c6f481ab67fe7709a7ee9c17c2157134111f81bf 100644 --- OP/op-e2e/eip4844_test.go +++ CELO/op-e2e/eip4844_test.go @@ -102,7 +102,7 @@ opts.Nonce = 1 // Already have deposit opts.ToAddr = &common.Address{0xff, 0xff} // put some random data in the tx to make it fill up 6 blobs (multi-blob case) opts.Data = testutils.RandomData(rand.New(rand.NewSource(420)), 400) - opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false) + opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false, nil) require.NoError(t, err) opts.VerifyOnClients(l2Verif) })
diff --git OP/op-e2e/faultproofs/bigCodeCreateInput.data CELO/op-e2e/faultproofs/bigCodeCreateInput.data new file mode 100644 index 0000000000000000000000000000000000000000..44891a39e07f912df925cccb2caa0808872d80ce --- /dev/null +++ CELO/op-e2e/faultproofs/bigCodeCreateInput.data @@ -0,0 +1 @@ +0x6080604052348015600f57600080fd5b50615fdd8061001f6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063dbd8cd6314610030575b600080fd5b61003861003a565b005b60405180615f800160405280615f4e815260200161005a615f4e91395056feaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2646970667358221220b25c21f2147000f10799a57b6475538b627899e60949e1142a24c6c19e21af7364736f6c63430008190033
diff --git OP/op-e2e/faultproofs/cannon_benchmark_test.go CELO/op-e2e/faultproofs/cannon_benchmark_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ecd128a3c956627baf258c1b06bac738f87ce0d9 --- /dev/null +++ CELO/op-e2e/faultproofs/cannon_benchmark_test.go @@ -0,0 +1,196 @@ +package faultproofs + +import ( + "context" + "crypto/ecdsa" + "encoding/json" + "math/big" + "os" + "path" + "sync" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func TestBenchmarkCannon_FPP(t *testing.T) { + t.Skip("TODO(client-pod#906): Compare total witness size for assertions against pages allocated by the VM") + + op_e2e.InitParallel(t, op_e2e.UsesCannon) + ctx := context.Background() + cfg := op_e2e.DefaultSystemConfig(t) + // We don't need a verifier - just the sequencer is enough + delete(cfg.Nodes, "verifier") + // Use a small sequencer window size to avoid test timeout while waiting for empty blocks + // But not too small to ensure that our claim and subsequent state change is published + cfg.DeployConfig.SequencerWindowSize = 16 + minTs := hexutil.Uint64(0) + cfg.DeployConfig.L2GenesisDeltaTimeOffset = &minTs + cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &minTs + + sys, err := cfg.Start(t) + require.Nil(t, err, "Error starting up system") + defer sys.Close() + + log := testlog.Logger(t, log.LevelInfo) + log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time) + + l1Client := sys.Clients["l1"] + l2Seq := sys.Clients["sequencer"] + rollupRPCClient, err := rpc.DialContext(context.Background(), sys.RollupNodes["sequencer"].HTTPEndpoint()) + require.Nil(t, err) + rollupClient := sources.NewRollupClient(client.NewBaseRPCClient(rollupRPCClient)) + require.NoError(t, wait.ForUnsafeBlock(ctx, rollupClient, 1)) + + // Agreed state: 200 Big Contracts deployed at max size - total codesize is 5.90 MB + // In Fault Proof: Perform multicalls calling each Big Contract + // - induces 200 oracle.CodeByHash preimage loads + // Assertion: Under 2000 pages requested by the program (i.e. max ~8 MB). Assumes derivation overhead; block finalization, etc, requires < 1 MB of program memory. + + const numCreates = 200 + newContracts := createBigContracts(ctx, t, cfg, l2Seq, cfg.Secrets.Alice, numCreates) + receipt := callBigContracts(ctx, t, cfg, l2Seq, cfg.Secrets.Alice, newContracts) + + t.Log("Capture the latest L2 head that preceedes contract creations as agreed starting point") + agreedBlock, err := l2Seq.BlockByNumber(ctx, new(big.Int).Sub(receipt.BlockNumber, big.NewInt(1))) + require.NoError(t, err) + agreedL2Output, err := rollupClient.OutputAtBlock(ctx, agreedBlock.NumberU64()) + require.NoError(t, err, "could not retrieve l2 agreed block") + l2Head := agreedL2Output.BlockRef.Hash + l2OutputRoot := agreedL2Output.OutputRoot + + t.Log("Determine L2 claim") + l2ClaimBlockNumber := receipt.BlockNumber + l2Output, err := rollupClient.OutputAtBlock(ctx, l2ClaimBlockNumber.Uint64()) + require.NoError(t, err, "could not get expected output") + l2Claim := l2Output.OutputRoot + + t.Log("Determine L1 head that includes all batches required for L2 claim block") + require.NoError(t, wait.ForSafeBlock(ctx, rollupClient, l2ClaimBlockNumber.Uint64())) + l1HeadBlock, err := l1Client.BlockByNumber(ctx, nil) + require.NoError(t, err, "get l1 head block") + l1Head := l1HeadBlock.Hash() + + inputs := utils.LocalGameInputs{ + L1Head: l1Head, + L2Head: l2Head, + L2Claim: common.Hash(l2Claim), + L2OutputRoot: common.Hash(l2OutputRoot), + L2BlockNumber: l2ClaimBlockNumber, + } + debugfile := path.Join(t.TempDir(), "debug.json") + runCannon(t, ctx, sys, inputs, "sequencer", "--debug-info", debugfile) + data, err := os.ReadFile(debugfile) + require.NoError(t, err) + var debuginfo mipsevm.DebugInfo + require.NoError(t, json.Unmarshal(data, &debuginfo)) + t.Logf("Debug info: %#v", debuginfo) + // TODO(client-pod#906): Use maximum witness size for assertions against pages allocated by the VM +} + +func createBigContracts(ctx context.Context, t *testing.T, cfg op_e2e.SystemConfig, client *ethclient.Client, key *ecdsa.PrivateKey, numContracts int) []common.Address { + /* + contract Big { + bytes constant foo = hex"<24.4 KB of random data>"; + function ekans() external { foo; } + } + */ + createInputHex, err := os.ReadFile("bigCodeCreateInput.data") + createInput := common.FromHex(string(createInputHex[2:])) + require.NoError(t, err) + + nonce, err := client.NonceAt(ctx, crypto.PubkeyToAddress(key.PublicKey), nil) + require.NoError(t, err) + + type result struct { + addr common.Address + err error + } + + var wg sync.WaitGroup + wg.Add(numContracts) + results := make(chan result, numContracts) + for i := 0; i < numContracts; i++ { + tx := types.MustSignNewTx(key, types.LatestSignerForChainID(cfg.L2ChainIDBig()), &types.DynamicFeeTx{ + ChainID: cfg.L2ChainIDBig(), + Nonce: nonce + uint64(i), + To: nil, + GasTipCap: big.NewInt(10), + GasFeeCap: big.NewInt(200), + Gas: 10_000_000, + Data: createInput, + }) + go func() { + defer wg.Done() + ctx, cancel := context.WithTimeout(ctx, 120*time.Second) + defer cancel() + err := client.SendTransaction(ctx, tx) + if err != nil { + results <- result{err: errors.Wrap(err, "Sending L2 tx")} + return + } + receipt, err := wait.ForReceiptOK(ctx, client, tx.Hash()) + if err != nil { + results <- result{err: errors.Wrap(err, "Waiting for receipt")} + return + } + results <- result{addr: receipt.ContractAddress, err: nil} + }() + } + wg.Wait() + close(results) + + var addrs []common.Address + for r := range results { + require.NoError(t, r.err) + addrs = append(addrs, r.addr) + } + return addrs +} + +func callBigContracts(ctx context.Context, t *testing.T, cfg op_e2e.SystemConfig, client *ethclient.Client, key *ecdsa.PrivateKey, addrs []common.Address) *types.Receipt { + multicall3, err := bindings.NewMultiCall3(predeploys.MultiCall3Addr, client) + require.NoError(t, err) + + chainID, err := client.ChainID(ctx) + require.NoError(t, err) + opts, err := bind.NewKeyedTransactorWithChainID(key, chainID) + require.NoError(t, err) + + var calls []bindings.Multicall3Call3Value + calldata := crypto.Keccak256([]byte("ekans()"))[:4] + for _, addr := range addrs { + calls = append(calls, bindings.Multicall3Call3Value{ + Target: addr, + CallData: calldata, + Value: new(big.Int), + }) + } + opts.GasLimit = 20_000_000 + tx, err := multicall3.Aggregate3Value(opts, calls) + require.NoError(t, err) + + receipt, err := wait.ForReceiptOK(ctx, client, tx.Hash()) + require.NoError(t, err) + t.Logf("Initiated %d calls to the Big Contract. gas used: %d", len(addrs), receipt.GasUsed) + return receipt +}
diff --git OP/op-e2e/faultproofs/precompile_test.go CELO/op-e2e/faultproofs/precompile_test.go index 28807edee6ddef6833d54d3c210b77abd616381e..ab8d0394771dc2476d711db5461d54b6c96cfb45 100644 --- OP/op-e2e/faultproofs/precompile_test.go +++ CELO/op-e2e/faultproofs/precompile_test.go @@ -10,8 +10,8 @@ "testing"   "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/op-challenger/config" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/metrics" op_e2e "github.com/ethereum-optimism/optimism/op-e2e" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" @@ -135,7 +135,7 @@ }) } }   -func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs utils.LocalGameInputs, l2Node string) { +func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs utils.LocalGameInputs, l2Node string, extraVmArgs ...string) { l1Endpoint := sys.NodeEndpoint("l1") l1Beacon := sys.L1BeaconEndpoint() rollupEndpoint := sys.RollupEndpoint("sequencer") @@ -147,10 +147,10 @@ cfg := config.NewConfig(common.Address{}, l1Endpoint, l1Beacon, rollupEndpoint, l2Endpoint, dir) cannonOpts(&cfg)   logger := testlog.Logger(t, log.LevelInfo).New("role", "cannon") - executor := cannon.NewExecutor(logger, metrics.NoopMetrics, &cfg, cfg.CannonAbsolutePreState, inputs) + executor := vm.NewExecutor(logger, metrics.NoopMetrics, cfg.Cannon, cfg.CannonAbsolutePreState, inputs)   t.Log("Running cannon") - err := executor.GenerateProof(ctx, proofsDir, math.MaxUint) + err := executor.DoGenerateProof(ctx, proofsDir, math.MaxUint, math.MaxUint, extraVmArgs...) require.NoError(t, err, "failed to generate proof")   state, err := parseState(filepath.Join(proofsDir, "final.json.gz"))
diff --git OP/op-e2e/sequencer_failover_setup.go CELO/op-e2e/sequencer_failover_setup.go index 4b4aeb20379b36c8e92a684a562602433ed50a48..f1fd867469e61e34c5ad64a9203294ca62cad466 100644 --- OP/op-e2e/sequencer_failover_setup.go +++ CELO/op-e2e/sequencer_failover_setup.go @@ -17,6 +17,7 @@ bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags" con "github.com/ethereum-optimism/optimism/op-conductor/conductor" + "github.com/ethereum-optimism/optimism/op-conductor/consensus" conrpc "github.com/ethereum-optimism/optimism/op-conductor/rpc" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" rollupNode "github.com/ethereum-optimism/optimism/op-node/node" @@ -74,8 +75,8 @@ c2 := conductors[Sequencer2Name] c3 := conductors[Sequencer3Name]   require.NoError(t, waitForLeadership(t, c1)) - require.NoError(t, c1.client.AddServerAsVoter(ctx, Sequencer2Name, c2.ConsensusEndpoint())) - require.NoError(t, c1.client.AddServerAsVoter(ctx, Sequencer3Name, c3.ConsensusEndpoint())) + require.NoError(t, c1.client.AddServerAsVoter(ctx, Sequencer2Name, c2.ConsensusEndpoint(), 0)) + require.NoError(t, c1.client.AddServerAsVoter(ctx, Sequencer3Name, c3.ConsensusEndpoint(), 0)) require.True(t, leader(t, ctx, c1)) require.False(t, leader(t, ctx, c2)) require.False(t, leader(t, ctx, c3)) @@ -508,3 +509,11 @@ return leaders == 1, nil } require.NoError(t, wait.For(ctx, 1*time.Second, condition)) } + +func memberIDs(membership *consensus.ClusterMembership) []string { + ids := make([]string, len(membership.Servers)) + for _, member := range membership.Servers { + ids = append(ids, member.ID) + } + return ids +}
diff --git OP/op-e2e/sequencer_failover_test.go CELO/op-e2e/sequencer_failover_test.go index 6d98dc2af9731a8f4a96b7f6d325cc499c44dc74..0fa38f54ba3c7865de040e4632354baf64703293 100644 --- OP/op-e2e/sequencer_failover_test.go +++ CELO/op-e2e/sequencer_failover_test.go @@ -5,6 +5,7 @@ "context" "sort" "testing"   + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require"   "github.com/ethereum-optimism/optimism/op-conductor/consensus" @@ -39,9 +40,9 @@ c2 := conductors[Sequencer2Name] c3 := conductors[Sequencer3Name] membership, err := c1.client.ClusterMembership(ctx) require.NoError(t, err) - require.Equal(t, 3, len(membership), "Expected 3 members in cluster") + require.Equal(t, 3, len(membership.Servers), "Expected 3 members in cluster") ids := make([]string, 0) - for _, member := range membership { + for _, member := range membership.Servers { ids = append(ids, member.ID) require.Equal(t, consensus.Voter, member.Suffrage, "Expected all members to be voters") } @@ -112,37 +113,54 @@ err = nonvoter.service.Stop(ctx) require.NoError(t, err) }()   - err = leader.client.AddServerAsNonvoter(ctx, VerifierName, nonvoter.ConsensusEndpoint()) + membership, err = leader.client.ClusterMembership(ctx) + require.NoError(t, err) + + err = leader.client.AddServerAsNonvoter(ctx, VerifierName, nonvoter.ConsensusEndpoint(), membership.Version-1) + require.ErrorContains(t, err, "configuration changed since", "Expected leader to fail to add nonvoter due to version mismatch") + membership, err = leader.client.ClusterMembership(ctx) + require.NoError(t, err) + require.Equal(t, 3, len(membership.Servers), "Expected 3 members in cluster") + + err = leader.client.AddServerAsNonvoter(ctx, VerifierName, nonvoter.ConsensusEndpoint(), 0) require.NoError(t, err, "Expected leader to add non-voter") membership, err = leader.client.ClusterMembership(ctx) require.NoError(t, err) - require.Equal(t, 4, len(membership), "Expected 4 members in cluster") - require.Equal(t, consensus.Nonvoter, membership[3].Suffrage, "Expected last member to be non-voter") + require.Equal(t, 4, len(membership.Servers), "Expected 4 members in cluster") + require.Equal(t, consensus.Nonvoter, membership.Servers[3].Suffrage, "Expected last member to be non-voter")   t.Log("Testing RemoveServer, call remove on follower, expected to fail") lid, leader = findLeader(t, conductors) fid, follower = findFollower(t, conductors) - err = follower.client.RemoveServer(ctx, lid) + err = follower.client.RemoveServer(ctx, lid, membership.Version) require.ErrorContains(t, err, "node is not the leader", "Expected follower to fail to remove leader") membership, err = c1.client.ClusterMembership(ctx) require.NoError(t, err) - require.Equal(t, 4, len(membership), "Expected 4 members in cluster") + require.Equal(t, 4, len(membership.Servers), "Expected 4 members in cluster")   t.Log("Testing RemoveServer, call remove on leader, expect non-voter to be removed") - err = leader.client.RemoveServer(ctx, VerifierName) + err = leader.client.RemoveServer(ctx, VerifierName, membership.Version) require.NoError(t, err, "Expected leader to remove non-voter") membership, err = c1.client.ClusterMembership(ctx) require.NoError(t, err) - require.Equal(t, 3, len(membership), "Expected 2 members in cluster after removal") - require.NotContains(t, membership, VerifierName, "Expected follower to be removed from cluster") + require.Equal(t, 3, len(membership.Servers), "Expected 2 members in cluster after removal") + require.NotContains(t, memberIDs(membership), VerifierName, "Expected follower to be removed from cluster") + + t.Log("Testing RemoveServer, call remove on leader with incorrect version, expect voter not to be removed") + err = leader.client.RemoveServer(ctx, fid, membership.Version-1) + require.ErrorContains(t, err, "configuration changed since", "Expected leader to fail to remove follower due to version mismatch") + membership, err = c1.client.ClusterMembership(ctx) + require.NoError(t, err) + require.Equal(t, 3, len(membership.Servers), "Expected 3 members in cluster after failed removal") + require.Contains(t, memberIDs(membership), fid, "Expected follower to not be removed from cluster")   t.Log("Testing RemoveServer, call remove on leader, expect voter to be removed") - err = leader.client.RemoveServer(ctx, fid) + err = leader.client.RemoveServer(ctx, fid, membership.Version) require.NoError(t, err, "Expected leader to remove follower") membership, err = c1.client.ClusterMembership(ctx) require.NoError(t, err) - require.Equal(t, 2, len(membership), "Expected 2 members in cluster after removal") - require.NotContains(t, membership, fid, "Expected follower to be removed from cluster") + require.Equal(t, 2, len(membership.Servers), "Expected 2 members in cluster after removal") + require.NotContains(t, memberIDs(membership), fid, "Expected follower to be removed from cluster") }   // [Category: Sequencer Failover] @@ -172,3 +190,44 @@ active, err := sys.RollupClient(newLeaderId).SequencerActive(ctx) require.NoError(t, err) require.True(t, active, "Expected new leader to be sequencing") } + +// [Category: Disaster Recovery] +// Test that sequencer can successfully be started with the overrideLeader flag set to true. +func TestSequencerFailover_DisasterRecovery_OverrideLeader(t *testing.T) { + sys, conductors, cleanup := setupSequencerFailoverTest(t) + defer cleanup() + + // randomly stop 2 nodes in the cluster to simulate a disaster. + ctx := context.Background() + err := conductors[Sequencer1Name].service.Stop(ctx) + require.NoError(t, err) + err = conductors[Sequencer2Name].service.Stop(ctx) + require.NoError(t, err) + + require.False(t, conductors[Sequencer3Name].service.Leader(ctx), "Expected sequencer to not be the leader") + active, err := sys.RollupClient(Sequencer3Name).SequencerActive(ctx) + require.NoError(t, err) + require.False(t, active, "Expected sequencer to be inactive") + + // Start sequencer without the overrideLeader flag set to true, should fail + err = sys.RollupClient(Sequencer3Name).StartSequencer(ctx, common.Hash{1, 2, 3}) + require.ErrorContains(t, err, "sequencer is not the leader, aborting.", "Expected sequencer to fail to start") + + // Start sequencer with the overrideLeader flag set to true, should succeed + err = sys.RollupClient(Sequencer3Name).OverrideLeader(ctx) + require.NoError(t, err) + blk, err := sys.NodeClient(Sequencer3Name).BlockByNumber(ctx, nil) + require.NoError(t, err) + err = sys.RollupClient(Sequencer3Name).StartSequencer(ctx, blk.Hash()) + require.NoError(t, err) + + active, err = sys.RollupClient(Sequencer3Name).SequencerActive(ctx) + require.NoError(t, err) + require.True(t, active, "Expected sequencer to be active") + + err = conductors[Sequencer3Name].client.OverrideLeader(ctx) + require.NoError(t, err) + leader, err := conductors[Sequencer3Name].client.Leader(ctx) + require.NoError(t, err) + require.True(t, leader, "Expected conductor to return leader true after override") +}
diff --git OP/op-e2e/setup.go CELO/op-e2e/setup.go index 2b255879180c15fbf4f70f898903d03eae68a23a..78120858c516c40ba48d44752e9e0ac6a12f9a27 100644 --- OP/op-e2e/setup.go +++ CELO/op-e2e/setup.go @@ -539,6 +539,7 @@ DeltaTime: cfg.DeployConfig.DeltaTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), EcotoneTime: cfg.DeployConfig.EcotoneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + Cel2Time: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, } }
diff --git OP/op-e2e/system_fpp_test.go CELO/op-e2e/system_fpp_test.go index 4da5f75ca51c185843e6681d229ee0a160b3b19d..635276fe159a8271a181eb0224690b173b9454fb 100644 --- OP/op-e2e/system_fpp_test.go +++ CELO/op-e2e/system_fpp_test.go @@ -6,20 +6,22 @@ "math/big" "testing" "time"   + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" - "github.com/ethereum-optimism/optimism/op-program/client/driver" + "github.com/ethereum-optimism/optimism/op-program/client/claim" opp "github.com/ethereum-optimism/optimism/op-program/host" oppconf "github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/require" )   func TestVerifyL2OutputRoot(t *testing.T) { @@ -320,7 +322,7 @@ err = opp.FaultProofProgram(ctx, log, fppConfig) if s.Detached { require.Error(t, err, "exit status 1") } else { - require.ErrorIs(t, err, driver.ErrClaimNotValid) + require.ErrorIs(t, err, claim.ErrClaimNotValid) } }
diff --git OP/op-e2e/system_test.go CELO/op-e2e/system_test.go index 4dcea8bd96c368c7565a60c500245594c630b25d..029a056782f83b40d7de77df4917c2a0140153b8 100644 --- OP/op-e2e/system_test.go +++ CELO/op-e2e/system_test.go @@ -11,6 +11,9 @@ "slices" "testing" "time"   + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + metrics2 "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -192,21 +195,21 @@ latestGameCount, err := disputeGameFactory.GameCount(&bind.CallOpts{}) require.Nil(t, err)   if latestGameCount.Cmp(initialGameCount) > 0 { + caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize) committedL2Output, err := disputeGameFactory.GameAtIndex(&bind.CallOpts{}, new(big.Int).Sub(latestGameCount, common.Big1)) require.Nil(t, err) - proxy, err := bindings.NewFaultDisputeGameCaller(committedL2Output.Proxy, l1Client) + proxy, err := contracts.NewFaultDisputeGameContract(context.Background(), metrics2.NoopContractMetrics, committedL2Output.Proxy, caller) require.Nil(t, err) - committedOutputRoot, err := proxy.RootClaim(&bind.CallOpts{}) + claim, err := proxy.GetClaim(context.Background(), 0) require.Nil(t, err)   ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - extradata, err := proxy.ExtraData(&bind.CallOpts{}) + _, gameBlockNumber, err := proxy.GetBlockRange(ctx) require.Nil(t, err) - gameBlockNumber := new(big.Int).SetBytes(extradata[0:32]) - l2Output, err := rollupClient.OutputAtBlock(ctx, gameBlockNumber.Uint64()) + l2Output, err := rollupClient.OutputAtBlock(ctx, gameBlockNumber) require.Nil(t, err) - require.Equal(t, l2Output.OutputRoot[:], committedOutputRoot[:]) + require.EqualValues(t, l2Output.OutputRoot, claim.Value) break }   @@ -614,7 +617,6 @@ // TestSystemMockP2P sets up a L1 Geth node, a rollup node, and a L2 geth node and then confirms that // the nodes can sync L2 blocks before they are confirmed on L1. func TestSystemMockP2P(t *testing.T) { - t.Skip("flaky in CI") // TODO(CLI-3859): Re-enable this test. InitParallel(t)   cfg := DefaultSystemConfig(t) @@ -1280,8 +1282,16 @@ require.Nil(t, err, "reading gpo decimals")   require.Equal(t, decimals.Uint64(), uint64(6), "wrong gpo decimals")   + // Celo changes the base fee recipient + var baseFeeRecipient common.Address + if sys.RollupConfig.Cel2Time == nil { + baseFeeRecipient = predeploys.BaseFeeVaultAddr + } else { + baseFeeRecipient = predeploys.FeeHandlerAddr + } + // BaseFee Recipient - baseFeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) + baseFeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), baseFeeRecipient, big.NewInt(rpc.EarliestBlockNumber.Int64())) require.Nil(t, err)   // L1Fee Recipient @@ -1324,7 +1334,7 @@ endBalance, err := l2Seq.BalanceAt(context.Background(), fromAddr, header.Number) require.Nil(t, err)   - baseFeeRecipientEndBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, header.Number) + baseFeeRecipientEndBalance, err := l2Seq.BalanceAt(context.Background(), baseFeeRecipient, header.Number) require.Nil(t, err)   l1Header, err := l1.HeaderByNumber(context.Background(), nil) @@ -1350,7 +1360,7 @@ require.Equal(t, l2Fee, sequencerFeeVaultDiff)   // Tally BaseFee baseFee := new(big.Int).Mul(header.BaseFee, new(big.Int).SetUint64(receipt.GasUsed)) - require.Equal(t, baseFee, baseFeeRecipientDiff, "base fee fee mismatch") + require.Equal(t, baseFee, baseFeeRecipientDiff, "base fee mismatch")   // Tally L1 Fee tx, _, err := l2Seq.TransactionByHash(context.Background(), receipt.TxHash)
diff --git OP/op-e2e/withdrawal_helper.go CELO/op-e2e/withdrawal_helper.go index 47bb9e8ddfa9e535a35a1a975bf5b8138d1b24cd..bc6b516c3001c187c36f6e41ba5103c5f916f077 100644 --- OP/op-e2e/withdrawal_helper.go +++ CELO/op-e2e/withdrawal_helper.go @@ -10,10 +10,10 @@ "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" - legacybindings "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-node/bindings" bindingspreview "github.com/ethereum-optimism/optimism/op-node/bindings/preview" @@ -201,9 +201,6 @@ game, err := portal2.ProvenWithdrawals(&bind.CallOpts{}, wdHash, opts.From) require.Nil(t, err) require.NotNil(t, game, "withdrawal should be proven")   - proxy, err := legacybindings.NewFaultDisputeGame(game.DisputeGameProxy, l1Client) - require.Nil(t, err) - caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize) gameContract, err := contracts.NewFaultDisputeGameContract(context.Background(), metrics.NoopContractMetrics, game.DisputeGameProxy, caller) require.Nil(t, err) @@ -216,19 +213,13 @@ t.Logf("Could not resolve dispute game claim: %v", err) return err == nil, nil }))   - resolveClaimTx, err := proxy.ResolveClaim(opts, common.Big0, common.Big0) - require.Nil(t, err) + tx, err := gameContract.ResolveClaimTx(0) + require.NoError(t, err, "create resolveClaim tx") + _, resolveClaimReceipt = transactions.RequireSendTx(t, ctx, l1Client, tx, privKey)   - resolveClaimReceipt, err = wait.ForReceiptOK(ctx, l1Client, resolveClaimTx.Hash()) - require.Nil(t, err, "resolve claim") - require.Equal(t, types.ReceiptStatusSuccessful, resolveClaimReceipt.Status) - - resolveTx, err := proxy.Resolve(opts) - require.Nil(t, err) - - resolveReceipt, err = wait.ForReceiptOK(ctx, l1Client, resolveTx.Hash()) - require.Nil(t, err, "resolve") - require.Equal(t, types.ReceiptStatusSuccessful, resolveReceipt.Status) + tx, err = gameContract.ResolveTx() + require.NoError(t, err, "create resolve tx") + _, resolveReceipt = transactions.RequireSendTx(t, ctx, l1Client, tx, privKey) }   if e2eutils.UseFaultProofs() {
diff --git OP/op-node/cmd/genesis/cmd.go CELO/op-node/cmd/genesis/cmd.go index 6d3d687444a24da3112f4157cb69f53daf3c0895..01c93b42164ee12450272f2ac961bb3ba96a2652 100644 --- OP/op-node/cmd/genesis/cmd.go +++ CELO/op-node/cmd/genesis/cmd.go @@ -16,6 +16,7 @@ "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc"   + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-service/jsonutil" ) @@ -112,9 +113,9 @@ if err := config.CheckAddresses(); err != nil { return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err) }   - var dump *genesis.ForgeAllocs + var dump *foundry.ForgeAllocs if l1Allocs := ctx.String("l1-allocs"); l1Allocs != "" { - dump, err = genesis.LoadForgeAllocs(l1Allocs) + dump, err = foundry.LoadForgeAllocs(l1Allocs) if err != nil { return err } @@ -169,9 +170,9 @@ return fmt.Errorf("cannot read L1 starting block at %s: %w", l1StartBlockPath, err) } }   - var l2Allocs *genesis.ForgeAllocs + var l2Allocs *foundry.ForgeAllocs if l2AllocsPath := ctx.String("l2-allocs"); l2AllocsPath != "" { - l2Allocs, err = genesis.LoadForgeAllocs(l2AllocsPath) + l2Allocs, err = foundry.LoadForgeAllocs(l2AllocsPath) if err != nil { return err }
diff --git OP/op-node/flags/flags.go CELO/op-node/flags/flags.go index 9e058350d1e18aaf15300a9d7a6f889736387f3a..59a7b0ef2de8152b87fa13e6677626adbf2bbdcf 100644 --- OP/op-node/flags/flags.go +++ CELO/op-node/flags/flags.go @@ -25,7 +25,7 @@ L1RPCCategory = "2. L1 RPC" SequencerCategory = "3. SEQUENCER" OperationsCategory = "4. LOGGING, METRICS, DEBUGGING, AND API" P2PCategory = "5. PEER-TO-PEER" - PlasmaCategory = "6. PLASMA (EXPERIMENTAL)" + AltDACategory = "6. ALT-DA (EXPERIMENTAL)" MiscCategory = "7. MISC" )   @@ -158,7 +158,7 @@ Category: L1RPCCategory, } L1RethDBPath = &cli.StringFlag{ Name: "l1.rethdb", - Usage: "The L1 RethDB path, used to fetch receipts for L1 blocks. Only applicable when using the `reth_db` RPC kind with `l1.rpckind`.", + Usage: "The L1 RethDB path, used to fetch receipts for L1 blocks.", EnvVars: prefixEnvVars("L1_RETHDB"), Hidden: true, Category: L1RPCCategory, @@ -424,7 +424,7 @@ optionalFlags = append(optionalFlags, oplog.CLIFlagsWithCategory(EnvVarPrefix, OperationsCategory)...) optionalFlags = append(optionalFlags, oppprof.CLIFlagsWithCategory(EnvVarPrefix, OperationsCategory)...) optionalFlags = append(optionalFlags, DeprecatedFlags...) optionalFlags = append(optionalFlags, opflags.CLIFlags(EnvVarPrefix, RollupCategory)...) - optionalFlags = append(optionalFlags, plasma.CLIFlags(EnvVarPrefix, PlasmaCategory)...) + optionalFlags = append(optionalFlags, plasma.CLIFlags(EnvVarPrefix, AltDACategory)...) Flags = append(requiredFlags, optionalFlags...) }
diff --git OP/op-node/flags/flags_test.go CELO/op-node/flags/flags_test.go index c7e87a59830b8eac7ca61404cb862c7b83a90266..3a94041c7e6ff3a12fab822d68177896e5e997f5 100644 --- OP/op-node/flags/flags_test.go +++ CELO/op-node/flags/flags_test.go @@ -5,6 +5,7 @@ "slices" "strings" "testing"   + plasma "github.com/ethereum-optimism/optimism/op-plasma" opservice "github.com/ethereum-optimism/optimism/op-service"   "github.com/stretchr/testify/require" @@ -73,7 +74,11 @@ func TestHasEnvVar(t *testing.T) { // known exceptions to the number of env vars expEnvVars := map[string]int{ - BeaconFallbackAddrs.Name: 2, + BeaconFallbackAddrs.Name: 2, + plasma.EnabledFlagName: 2, + plasma.DaServerAddressFlagName: 2, + plasma.VerifyOnReadFlagName: 2, + plasma.DaServiceFlag: 2, }   for _, flag := range Flags {
diff --git OP/op-node/node/api.go CELO/op-node/node/api.go index cb60d22d911b3267678130d1748e76f05c40bc29..a94e2477fe169b23537df5e56caccf5510fa816a 100644 --- OP/op-node/node/api.go +++ CELO/op-node/node/api.go @@ -5,11 +5,11 @@ "context" "errors" "fmt"   - "github.com/ethereum-optimism/optimism/op-node/node/safedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log"   + "github.com/ethereum-optimism/optimism/op-node/node/safedb" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/version" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -33,6 +33,7 @@ StartSequencer(ctx context.Context, blockHash common.Hash) error StopSequencer(context.Context) (common.Hash, error) SequencerActive(context.Context) (bool, error) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error + OverrideLeader(ctx context.Context) error }   type SafeDBReader interface { @@ -77,7 +78,6 @@ }   // PostUnsafePayload is a special API that allows posting an unsafe payload to the L2 derivation pipeline. // It should only be used by op-conductor for sequencer failover scenarios. -// TODO(ethereum-optimism/optimism#9064): op-conductor Dencun changes. func (n *adminAPI) PostUnsafePayload(ctx context.Context, envelope *eth.ExecutionPayloadEnvelope) error { recordDur := n.M.RecordRPCServerRequest("admin_postUnsafePayload") defer recordDur() @@ -89,6 +89,13 @@ return fmt.Errorf("payload has bad block hash: %s, actual block hash is: %s", payload.BlockHash.String(), actual.String()) }   return n.dr.OnUnsafeL2Payload(ctx, envelope) +} + +// OverrideLeader disables sequencer conductor interactions and allow sequencer to run in non-HA mode during disaster recovery scenarios. +func (n *adminAPI) OverrideLeader(ctx context.Context) error { + recordDur := n.M.RecordRPCServerRequest("admin_overrideLeader") + defer recordDur() + return n.dr.OverrideLeader(ctx) }   type nodeAPI struct {
diff --git OP/op-node/node/conductor.go CELO/op-node/node/conductor.go index 938b9f28c5b1d89f2af3f6dc9d5f242b38a3d7c2..20e0638dc6869305b02d17ee524261dcc9e1b08a 100644 --- OP/op-node/node/conductor.go +++ CELO/op-node/node/conductor.go @@ -3,16 +3,17 @@ import ( "context" "fmt" + "sync/atomic" "time"   + "github.com/ethereum/go-ethereum/log" + + conductorRpc "github.com/ethereum-optimism/optimism/op-conductor/rpc" "github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/rollup/conductor" "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/retry" - "github.com/ethereum/go-ethereum/log" - - conductorRpc "github.com/ethereum-optimism/optimism/op-conductor/rpc" )   // ConductorClient is a client for the op-conductor RPC service. @@ -21,13 +22,22 @@ cfg *Config metrics *metrics.Metrics log log.Logger apiClient *conductorRpc.APIClient + + // overrideLeader is used to override the leader check for disaster recovery purposes. + // During disaster situations where the cluster is unhealthy (no leader, only 1 or less nodes up), + // set this to true to allow the node to assume sequencing responsibilities without being the leader. + overrideLeader atomic.Bool }   var _ conductor.SequencerConductor = &ConductorClient{}   // NewConductorClient returns a new conductor client for the op-conductor RPC service. func NewConductorClient(cfg *Config, log log.Logger, metrics *metrics.Metrics) *ConductorClient { - return &ConductorClient{cfg: cfg, metrics: metrics, log: log} + return &ConductorClient{ + cfg: cfg, + metrics: metrics, + log: log, + } }   // Initialize initializes the conductor client. @@ -45,6 +55,10 @@ }   // Leader returns true if this node is the leader sequencer. func (c *ConductorClient) Leader(ctx context.Context) (bool, error) { + if c.overrideLeader.Load() { + return true, nil + } + if err := c.initialize(); err != nil { return false, err } @@ -62,6 +76,10 @@ }   // CommitUnsafePayload commits an unsafe payload to the conductor log. func (c *ConductorClient) CommitUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error { + if c.overrideLeader.Load() { + return nil + } + if err := c.initialize(); err != nil { return err } @@ -76,6 +94,12 @@ record(err) return true, err }) return err +} + +// OverrideLeader implements conductor.SequencerConductor. +func (c *ConductorClient) OverrideLeader(ctx context.Context) error { + c.overrideLeader.Store(true) + return nil }   func (c *ConductorClient) Close() {
diff --git OP/op-node/node/config.go CELO/op-node/node/config.go index 223afaccf0f936649d9824de09172e5e0e0e5eca..0688c8a6cc755ee5c9693ac58a445beabef97ae1 100644 --- OP/op-node/node/config.go +++ CELO/op-node/node/config.go @@ -175,7 +175,7 @@ if err := cfg.Plasma.Check(); err != nil { return fmt.Errorf("plasma config error: %w", err) } if cfg.Plasma.Enabled { - log.Warn("Plasma Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues.") + log.Warn("Alt-DA Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues.") } return nil }
diff --git OP/op-node/node/node.go CELO/op-node/node/node.go index f2a62cf58da0da0cd5e295a7ebd0ec7976ef2a57..fb5f981d8f14af1af154b609bc8c0a60a3a79e21 100644 --- OP/op-node/node/node.go +++ CELO/op-node/node/node.go @@ -582,7 +582,8 @@ }   n.tracer.OnUnsafeL2Payload(ctx, from, envelope)   - n.log.Info("Received signed execution payload from p2p", "id", envelope.ExecutionPayload.ID(), "peer", from) + n.log.Info("Received signed execution payload from p2p", "id", envelope.ExecutionPayload.ID(), "peer", from, + "txs", len(envelope.ExecutionPayload.Transactions))   // Pass on the event to the L2 Engine ctx, cancel := context.WithTimeout(ctx, time.Second*30)
diff --git OP/op-node/node/server_test.go CELO/op-node/node/server_test.go index 587befd6de805483a76792e483b14d89947299a7..7063b3ed2807cdc52065ffabe37e2b6a5a51f1a4 100644 --- OP/op-node/node/server_test.go +++ CELO/op-node/node/server_test.go @@ -283,6 +283,10 @@ func (c *mockDriverClient) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error { return c.Mock.MethodCalled("OnUnsafeL2Payload").Get(0).(error) }   +func (c *mockDriverClient) OverrideLeader(ctx context.Context) error { + return c.Mock.MethodCalled("OverrideLeader").Get(0).(error) +} + type mockSafeDBReader struct { mock.Mock }
diff --git OP/op-node/rollup/attributes/attributes.go CELO/op-node/rollup/attributes/attributes.go index 517c91f708633b5a51d981323c97038254a00942..99bd123598ac1ebb78ab1cdf9a526d5d79239a93 100644 --- OP/op-node/rollup/attributes/attributes.go +++ CELO/op-node/rollup/attributes/attributes.go @@ -4,33 +4,18 @@ import ( "context" "errors" "fmt" - "io" + "sync" "time"   "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-node/rollup/async" - "github.com/ethereum-optimism/optimism/op-node/rollup/conductor" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-service/eth" )   -type Engine interface { - engine.EngineControl - - SetUnsafeHead(eth.L2BlockRef) - SetSafeHead(eth.L2BlockRef) - SetBackupUnsafeL2Head(block eth.L2BlockRef, triggerReorg bool) - SetPendingSafeL2Head(eth.L2BlockRef) - - PendingSafeL2Head() eth.L2BlockRef - BackupUnsafeL2Head() eth.L2BlockRef -} - type L2 interface { PayloadByNumber(context.Context, uint64) (*eth.ExecutionPayloadEnvelope, error) } @@ -39,150 +24,134 @@ type AttributesHandler struct { log log.Logger cfg *rollup.Config   - ec Engine + // when the rollup node shuts down, stop any in-flight sub-processes of the attributes-handler + ctx context.Context + l2 L2   + mu sync.Mutex + + emitter rollup.EventEmitter + attributes *derive.AttributesWithParent }   -func NewAttributesHandler(log log.Logger, cfg *rollup.Config, ec Engine, l2 L2) *AttributesHandler { +func NewAttributesHandler(log log.Logger, cfg *rollup.Config, ctx context.Context, l2 L2, emitter rollup.EventEmitter) *AttributesHandler { return &AttributesHandler{ log: log, cfg: cfg, - ec: ec, + ctx: ctx, l2: l2, + emitter: emitter, attributes: nil, } }   -func (eq *AttributesHandler) HasAttributes() bool { - return eq.attributes != nil -} +func (eq *AttributesHandler) OnEvent(ev rollup.Event) { + // Events may be concurrent in the future. Prevent unsafe concurrent modifications to the attributes. + eq.mu.Lock() + defer eq.mu.Unlock()   -func (eq *AttributesHandler) SetAttributes(attributes *derive.AttributesWithParent) { - eq.attributes = attributes + switch x := ev.(type) { + case engine.PendingSafeUpdateEvent: + eq.onPendingSafeUpdate(x) + case derive.DerivedAttributesEvent: + eq.attributes = x.Attributes + eq.emitter.Emit(derive.ConfirmReceivedAttributesEvent{}) + // to make sure we have a pre-state signal to process the attributes from + eq.emitter.Emit(engine.PendingSafeRequestEvent{}) + case engine.InvalidPayloadAttributesEvent: + // If the engine signals that attributes are invalid, + // that should match our last applied attributes, which we should thus drop. + eq.attributes = nil + // Time to re-evaluate without attributes. + // (the pending-safe state will then be forwarded to our source of attributes). + eq.emitter.Emit(engine.PendingSafeRequestEvent{}) + } }   -// Proceed processes block attributes, if any. -// Proceed returns io.EOF if there are no attributes to process. -// Proceed returns a temporary, reset, or critical error like other derivers. -// Proceed returns no error if the safe-head may have changed. -func (eq *AttributesHandler) Proceed(ctx context.Context) error { +// onPendingSafeUpdate applies the queued-up block attributes, if any, on top of the signaled pending state. +// The event is also used to clear the queued-up attributes, when successfully processed. +// On processing failure this may emit a temporary, reset, or critical error like other derivers. +func (eq *AttributesHandler) onPendingSafeUpdate(x engine.PendingSafeUpdateEvent) { + if x.Unsafe.Number < x.PendingSafe.Number { + // invalid chain state, reset to try and fix it + eq.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("pending-safe label (%d) may not be ahead of unsafe head label (%d)", x.PendingSafe.Number, x.Unsafe.Number)}) + return + } + if eq.attributes == nil { - return io.EOF + // Request new attributes to be generated, only if we don't currently have attributes that have yet to be processed. + // It is safe to request the pipeline, the attributes-handler is the only user of it, + // and the pipeline will not generate another set of attributes until the last set is recognized. + eq.emitter.Emit(derive.PipelineStepEvent{PendingSafe: x.PendingSafe}) + return } - // validate the safe attributes before processing them. The engine may have completed processing them through other means. - if eq.ec.PendingSafeL2Head() != eq.attributes.Parent { - // Previously the attribute's parent was the pending safe head. If the pending safe head advances so pending safe head's parent is the same as the - // attribute's parent then we need to cancel the attributes. - if eq.ec.PendingSafeL2Head().ParentHash == eq.attributes.Parent.Hash { - eq.log.Warn("queued safe attributes are stale, safehead progressed", - "pending_safe_head", eq.ec.PendingSafeL2Head(), "pending_safe_head_parent", eq.ec.PendingSafeL2Head().ParentID(), - "attributes_parent", eq.attributes.Parent) - eq.attributes = nil - return nil - } - // If something other than a simple advance occurred, perform a full reset - return derive.NewResetError(fmt.Errorf("pending safe head changed to %s with parent %s, conflicting with queued safe attributes on top of %s", - eq.ec.PendingSafeL2Head(), eq.ec.PendingSafeL2Head().ParentID(), eq.attributes.Parent)) + + // Drop attributes if they don't apply on top of the pending safe head + if eq.attributes.Parent.Number != x.PendingSafe.Number { + eq.log.Warn("dropping stale attributes", + "pending", x.PendingSafe, "attributes_parent", eq.attributes.Parent) + eq.attributes = nil + return } - if eq.ec.PendingSafeL2Head().Number < eq.ec.UnsafeL2Head().Number { - if err := eq.consolidateNextSafeAttributes(ctx, eq.attributes); err != nil { - return err - } - eq.attributes = nil - return nil - } else if eq.ec.PendingSafeL2Head().Number == eq.ec.UnsafeL2Head().Number { - if err := eq.forceNextSafeAttributes(ctx, eq.attributes); err != nil { - return err + + if eq.attributes.Parent != x.PendingSafe { + // If the attributes are supposed to follow the pending safe head, but don't build on the exact block, + // then there's some reorg inconsistency. Either bad attributes, or bad pending safe head. + // Trigger a reset, and the system can derive attributes on top of the pending safe head. + // Until the reset is complete we don't clear the attributes state, + // so we can re-emit the ResetEvent until the reset actually happens. + + eq.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("pending safe head changed to %s with parent %s, conflicting with queued safe attributes on top of %s", + x.PendingSafe, x.PendingSafe.ParentID(), eq.attributes.Parent)}) + } else { + // if there already exists a block we can just consolidate it + if x.PendingSafe.Number < x.Unsafe.Number { + eq.consolidateNextSafeAttributes(eq.attributes, x.PendingSafe) + } else { + // append to tip otherwise + eq.emitter.Emit(engine.ProcessAttributesEvent{Attributes: eq.attributes}) } - eq.attributes = nil - return nil - } else { - // For some reason the unsafe head is behind the pending safe head. Log it, and correct it. - eq.log.Error("invalid sync state, unsafe head is behind pending safe head", "unsafe", eq.ec.UnsafeL2Head(), "pending_safe", eq.ec.PendingSafeL2Head()) - eq.ec.SetUnsafeHead(eq.ec.PendingSafeL2Head()) - return nil } }   // consolidateNextSafeAttributes tries to match the next safe attributes against the existing unsafe chain, // to avoid extra processing or unnecessary unwinding of the chain. -// However, if the attributes do not match, they will be forced with forceNextSafeAttributes. -func (eq *AttributesHandler) consolidateNextSafeAttributes(ctx context.Context, attributes *derive.AttributesWithParent) error { - ctx, cancel := context.WithTimeout(ctx, time.Second*10) +// However, if the attributes do not match, they will be forced to process the attributes. +func (eq *AttributesHandler) consolidateNextSafeAttributes(attributes *derive.AttributesWithParent, onto eth.L2BlockRef) { + ctx, cancel := context.WithTimeout(eq.ctx, time.Second*10) defer cancel()   - envelope, err := eq.l2.PayloadByNumber(ctx, eq.ec.PendingSafeL2Head().Number+1) + envelope, err := eq.l2.PayloadByNumber(ctx, attributes.Parent.Number+1) if err != nil { if errors.Is(err, ethereum.NotFound) { // engine may have restarted, or inconsistent safe head. We need to reset - return derive.NewResetError(fmt.Errorf("expected engine was synced and had unsafe block to reconcile, but cannot find the block: %w", err)) + eq.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("expected engine was synced and had unsafe block to reconcile, but cannot find the block: %w", err)}) + return } - return derive.NewTemporaryError(fmt.Errorf("failed to get existing unsafe payload to compare against derived attributes from L1: %w", err)) + eq.emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: fmt.Errorf("failed to get existing unsafe payload to compare against derived attributes from L1: %w", err)}) + return } - if err := AttributesMatchBlock(eq.cfg, attributes.Attributes, eq.ec.PendingSafeL2Head().Hash, envelope, eq.log); err != nil { - eq.log.Warn("L2 reorg: existing unsafe block does not match derived attributes from L1", "err", err, "unsafe", eq.ec.UnsafeL2Head(), "pending_safe", eq.ec.PendingSafeL2Head(), "safe", eq.ec.SafeL2Head()) + if err := AttributesMatchBlock(eq.cfg, attributes.Attributes, onto.Hash, envelope, eq.log); err != nil { + eq.log.Warn("L2 reorg: existing unsafe block does not match derived attributes from L1", + "err", err, "unsafe", envelope.ExecutionPayload.ID(), "pending_safe", onto) + // geth cannot wind back a chain without reorging to a new, previously non-canonical, block - return eq.forceNextSafeAttributes(ctx, attributes) + eq.emitter.Emit(engine.ProcessAttributesEvent{Attributes: attributes}) + return + } else { + ref, err := derive.PayloadToBlockRef(eq.cfg, envelope.ExecutionPayload) + if err != nil { + eq.log.Error("Failed to compute block-ref from execution payload") + return + } + eq.emitter.Emit(engine.PromotePendingSafeEvent{ + Ref: ref, + Safe: attributes.IsLastInSpan, + DerivedFrom: attributes.DerivedFrom, + }) } - ref, err := derive.PayloadToBlockRef(eq.cfg, envelope.ExecutionPayload) - if err != nil { - return derive.NewResetError(fmt.Errorf("failed to decode L2 block ref from payload: %w", err)) - } - eq.ec.SetPendingSafeL2Head(ref) - if attributes.IsLastInSpan { - eq.ec.SetSafeHead(ref) - } + // unsafe head stays the same, we did not reorg the chain. - return nil -} - -// forceNextSafeAttributes inserts the provided attributes, reorging away any conflicting unsafe chain. -func (eq *AttributesHandler) forceNextSafeAttributes(ctx context.Context, attributes *derive.AttributesWithParent) error { - attrs := attributes.Attributes - errType, err := eq.ec.StartPayload(ctx, eq.ec.PendingSafeL2Head(), attributes, true) - if err == nil { - _, errType, err = eq.ec.ConfirmPayload(ctx, async.NoOpGossiper{}, &conductor.NoOpConductor{}) - } - if err != nil { - switch errType { - case engine.BlockInsertTemporaryErr: - // RPC errors are recoverable, we can retry the buffered payload attributes later. - return derive.NewTemporaryError(fmt.Errorf("temporarily cannot insert new safe block: %w", err)) - case engine.BlockInsertPrestateErr: - _ = eq.ec.CancelPayload(ctx, true) - return derive.NewResetError(fmt.Errorf("need reset to resolve pre-state problem: %w", err)) - case engine.BlockInsertPayloadErr: - _ = eq.ec.CancelPayload(ctx, true) - eq.log.Warn("could not process payload derived from L1 data, dropping batch", "err", err) - // Count the number of deposits to see if the tx list is deposit only. - depositCount := 0 - for _, tx := range attrs.Transactions { - if len(tx) > 0 && tx[0] == types.DepositTxType { - depositCount += 1 - } - } - // Deposit transaction execution errors are suppressed in the execution engine, but if the - // block is somehow invalid, there is nothing we can do to recover & we should exit. - if len(attrs.Transactions) == depositCount { - eq.log.Error("deposit only block was invalid", "parent", attributes.Parent, "err", err) - return derive.NewCriticalError(fmt.Errorf("failed to process block with only deposit transactions: %w", err)) - } - // Revert the pending safe head to the safe head. - eq.ec.SetPendingSafeL2Head(eq.ec.SafeL2Head()) - // suppress the error b/c we want to retry with the next batch from the batch queue - // If there is no valid batch the node will eventually force a deposit only block. If - // the deposit only block fails, this will return the critical error above. - - // Try to restore to previous known unsafe chain. - eq.ec.SetBackupUnsafeL2Head(eq.ec.BackupUnsafeL2Head(), true) - - // drop the payload (by returning no error) without inserting it into the engine - return nil - default: - return derive.NewCriticalError(fmt.Errorf("unknown InsertHeadBlock error type %d: %w", errType, err)) - } - } - return nil }
diff --git OP/op-node/rollup/attributes/attributes_test.go CELO/op-node/rollup/attributes/attributes_test.go index 62931af8c3786f351a55bfbf338fac3b9251c674..1833604ff31755ac9df06190a88c28270a50fa97 100644 --- OP/op-node/rollup/attributes/attributes_test.go +++ CELO/op-node/rollup/attributes/attributes_test.go @@ -2,7 +2,6 @@ package attributes   import ( "context" - "io" "math/big" "math/rand" // nosemgrep "testing" @@ -14,11 +13,9 @@ "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log"   - "github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/engine" - "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testutils" @@ -27,6 +24,13 @@ func TestAttributesHandler(t *testing.T) { rng := rand.New(rand.NewSource(1234)) refA := testutils.RandomBlockRef(rng) + + refB := eth.L1BlockRef{ + Hash: testutils.RandomHash(rng), + Number: refA.Number + 1, + ParentHash: refA.Hash, + Time: refA.Time + 12, + }   aL1Info := &testutils.MockBlockInfo{ InfoParentHash: refA.ParentHash, @@ -153,161 +157,149 @@ refA1Alt, err := derive.PayloadToBlockRef(cfg, payloadA1Alt.ExecutionPayload) require.NoError(t, err)   - refA2 := eth.L2BlockRef{ - Hash: testutils.RandomHash(rng), - Number: refA1.Number + 1, - ParentHash: refA1.Hash, - Time: refA1.Time + cfg.BlockTime, - L1Origin: refA.ID(), - SequenceNumber: 1, - } + t.Run("drop invalid attributes", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + l2 := &testutils.MockL2Client{} + emitter := &testutils.MockEmitter{} + ah := NewAttributesHandler(logger, cfg, context.Background(), l2, emitter)   - a2L1Info, err := derive.L1InfoDepositBytes(cfg, cfg.Genesis.SystemConfig, refA2.SequenceNumber, aL1Info, refA2.Time) - require.NoError(t, err) - attrA2 := &derive.AttributesWithParent{ - Attributes: &eth.PayloadAttributes{ - Timestamp: eth.Uint64Quantity(refA2.Time), - PrevRandao: eth.Bytes32{}, - SuggestedFeeRecipient: common.Address{}, - Withdrawals: nil, - ParentBeaconBlockRoot: &common.Hash{}, - Transactions: []eth.Data{a2L1Info}, - NoTxPool: false, - GasLimit: &gasLimit, - }, - Parent: refA1, - IsLastInSpan: true, - } + emitter.ExpectOnce(derive.ConfirmReceivedAttributesEvent{}) + emitter.ExpectOnce(engine.PendingSafeRequestEvent{}) + ah.OnEvent(derive.DerivedAttributesEvent{ + Attributes: attrA1, + }) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes, "queue the invalid attributes")   + emitter.ExpectOnce(engine.PendingSafeRequestEvent{}) + ah.OnEvent(engine.InvalidPayloadAttributesEvent{ + Attributes: attrA1, + }) + emitter.AssertExpectations(t) + require.Nil(t, ah.attributes, "drop the invalid attributes") + }) t.Run("drop stale attributes", func(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) - eng := &testutils.MockEngine{} - ec := engine.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, sync.CLSync) - ah := NewAttributesHandler(logger, cfg, ec, eng) - defer eng.AssertExpectations(t) + l2 := &testutils.MockL2Client{} + emitter := &testutils.MockEmitter{} + ah := NewAttributesHandler(logger, cfg, context.Background(), l2, emitter)   - ec.SetPendingSafeL2Head(refA1Alt) - ah.SetAttributes(attrA1) - require.True(t, ah.HasAttributes()) - require.NoError(t, ah.Proceed(context.Background()), "drop stale attributes") - require.False(t, ah.HasAttributes()) + emitter.ExpectOnce(derive.ConfirmReceivedAttributesEvent{}) + emitter.ExpectOnce(engine.PendingSafeRequestEvent{}) + ah.OnEvent(derive.DerivedAttributesEvent{ + Attributes: attrA1, + }) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes) + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA1Alt, + Unsafe: refA1Alt, + }) + l2.AssertExpectations(t) + emitter.AssertExpectations(t) + require.Nil(t, ah.attributes, "drop stale attributes") })   t.Run("pending gets reorged", func(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) - eng := &testutils.MockEngine{} - ec := engine.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, sync.CLSync) - ah := NewAttributesHandler(logger, cfg, ec, eng) - defer eng.AssertExpectations(t) + l2 := &testutils.MockL2Client{} + emitter := &testutils.MockEmitter{} + ah := NewAttributesHandler(logger, cfg, context.Background(), l2, emitter) + + emitter.ExpectOnce(derive.ConfirmReceivedAttributesEvent{}) + emitter.ExpectOnce(engine.PendingSafeRequestEvent{}) + ah.OnEvent(derive.DerivedAttributesEvent{ + Attributes: attrA1, + }) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes)   - ec.SetPendingSafeL2Head(refA0Alt) - ah.SetAttributes(attrA1) - require.True(t, ah.HasAttributes()) - require.ErrorIs(t, ah.Proceed(context.Background()), derive.ErrReset, "A1 does not fit on A0Alt") - require.True(t, ah.HasAttributes(), "detected reorg does not clear state, reset is required") + emitter.ExpectOnceType("ResetEvent") + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA0Alt, + Unsafe: refA0Alt, + }) + l2.AssertExpectations(t) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes, "detected reorg does not clear state, reset is required") })   t.Run("pending older than unsafe", func(t *testing.T) { t.Run("consolidation fails", func(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) - eng := &testutils.MockEngine{} - ec := engine.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, sync.CLSync) - ah := NewAttributesHandler(logger, cfg, ec, eng) + l2 := &testutils.MockL2Client{} + emitter := &testutils.MockEmitter{} + ah := NewAttributesHandler(logger, cfg, context.Background(), l2, emitter)   - ec.SetUnsafeHead(refA1) - ec.SetSafeHead(refA0) - ec.SetFinalizedHead(refA0) - ec.SetPendingSafeL2Head(refA0) - - defer eng.AssertExpectations(t) + // attrA1Alt does not match block A1, so will cause force-reorg. + emitter.ExpectOnce(derive.ConfirmReceivedAttributesEvent{}) + emitter.ExpectOnce(engine.PendingSafeRequestEvent{}) + ah.OnEvent(derive.DerivedAttributesEvent{Attributes: attrA1Alt}) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes, "queued up derived attributes")   // Call during consolidation. // The payloadA1 is going to get reorged out in favor of attrA1Alt (turns into payloadA1Alt) - eng.ExpectPayloadByNumber(refA1.Number, payloadA1, nil) + l2.ExpectPayloadByNumber(refA1.Number, payloadA1, nil) + // fail consolidation, perform force reorg + emitter.ExpectOnce(engine.ProcessAttributesEvent{Attributes: attrA1Alt}) + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA0, + Unsafe: refA1, + }) + l2.AssertExpectations(t) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes, "still have attributes, processing still unconfirmed")   - // attrA1Alt does not match block A1, so will cause force-reorg. - { - eng.ExpectForkchoiceUpdate(&eth.ForkchoiceState{ - HeadBlockHash: payloadA1Alt.ExecutionPayload.ParentHash, // reorg - SafeBlockHash: refA0.Hash, - FinalizedBlockHash: refA0.Hash, - }, attrA1Alt.Attributes, &eth.ForkchoiceUpdatedResult{ - PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionValid}, - PayloadID: &eth.PayloadID{1, 2, 3}, - }, nil) // to build the block - eng.ExpectGetPayload(eth.PayloadID{1, 2, 3}, payloadA1Alt, nil) - eng.ExpectNewPayload(payloadA1Alt.ExecutionPayload, payloadA1Alt.ParentBeaconBlockRoot, - &eth.PayloadStatusV1{Status: eth.ExecutionValid}, nil) // to persist the block - eng.ExpectForkchoiceUpdate(&eth.ForkchoiceState{ - HeadBlockHash: payloadA1Alt.ExecutionPayload.BlockHash, - SafeBlockHash: payloadA1Alt.ExecutionPayload.BlockHash, - FinalizedBlockHash: refA0.Hash, - }, nil, &eth.ForkchoiceUpdatedResult{ - PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionValid}, - PayloadID: nil, - }, nil) // to make it canonical - } - - ah.SetAttributes(attrA1Alt) - - require.True(t, ah.HasAttributes()) - require.NoError(t, ah.Proceed(context.Background()), "fail consolidation, perform force reorg") - require.False(t, ah.HasAttributes()) - - require.Equal(t, refA1Alt.Hash, payloadA1Alt.ExecutionPayload.BlockHash, "hash") - t.Log("ref A1: ", refA1.Hash) - t.Log("ref A0: ", refA0.Hash) - t.Log("ref alt: ", refA1Alt.Hash) - require.Equal(t, refA1Alt, ec.UnsafeL2Head(), "unsafe head reorg complete") - require.Equal(t, refA1Alt, ec.SafeL2Head(), "safe head reorg complete and updated") + // recognize reorg as complete + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA1Alt, + Unsafe: refA1Alt, + }) + emitter.AssertExpectations(t) + require.Nil(t, ah.attributes, "drop when attributes are successful") }) t.Run("consolidation passes", func(t *testing.T) { fn := func(t *testing.T, lastInSpan bool) { logger := testlog.Logger(t, log.LevelInfo) - eng := &testutils.MockEngine{} - ec := engine.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, sync.CLSync) - ah := NewAttributesHandler(logger, cfg, ec, eng) - - ec.SetUnsafeHead(refA1) - ec.SetSafeHead(refA0) - ec.SetFinalizedHead(refA0) - ec.SetPendingSafeL2Head(refA0) - - defer eng.AssertExpectations(t) - - // Call during consolidation. - eng.ExpectPayloadByNumber(refA1.Number, payloadA1, nil) - - expectedSafeHash := refA0.Hash - if lastInSpan { // if last in span, then it becomes safe - expectedSafeHash = refA1.Hash - } - eng.ExpectForkchoiceUpdate(&eth.ForkchoiceState{ - HeadBlockHash: refA1.Hash, - SafeBlockHash: expectedSafeHash, - FinalizedBlockHash: refA0.Hash, - }, nil, &eth.ForkchoiceUpdatedResult{ - PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionValid}, - PayloadID: nil, - }, nil) + l2 := &testutils.MockL2Client{} + emitter := &testutils.MockEmitter{} + ah := NewAttributesHandler(logger, cfg, context.Background(), l2, emitter)   attr := &derive.AttributesWithParent{ Attributes: attrA1.Attributes, // attributes will match, passing consolidation Parent: attrA1.Parent, IsLastInSpan: lastInSpan, + DerivedFrom: refB, } - ah.SetAttributes(attr) + emitter.ExpectOnce(derive.ConfirmReceivedAttributesEvent{}) + emitter.ExpectOnce(engine.PendingSafeRequestEvent{}) + ah.OnEvent(derive.DerivedAttributesEvent{Attributes: attr}) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes, "queued up derived attributes") + + // Call during consolidation. + l2.ExpectPayloadByNumber(refA1.Number, payloadA1, nil) + + emitter.ExpectOnce(engine.PromotePendingSafeEvent{ + Ref: refA1, + Safe: lastInSpan, // last in span becomes safe instantaneously + DerivedFrom: refB, + }) + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA0, + Unsafe: refA1, + }) + l2.AssertExpectations(t) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes, "still have attributes, processing still unconfirmed")   - require.True(t, ah.HasAttributes()) - require.NoError(t, ah.Proceed(context.Background()), "consolidate") - require.False(t, ah.HasAttributes()) - require.NoError(t, ec.TryUpdateEngine(context.Background()), "update to handle safe bump (lastinspan case)") - if lastInSpan { - require.Equal(t, refA1, ec.SafeL2Head(), "last in span becomes safe instantaneously") - } else { - require.Equal(t, refA1, ec.PendingSafeL2Head(), "pending as safe") - require.Equal(t, refA0, ec.SafeL2Head(), "A1 not yet safe") - } + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA1, + Unsafe: refA1, + }) + emitter.AssertExpectations(t) + require.Nil(t, ah.attributes, "drop when attributes are successful") } t.Run("is last span", func(t *testing.T) { fn(t, true) @@ -321,89 +313,70 @@ })   t.Run("pending equals unsafe", func(t *testing.T) { // no consolidation to do, just force next attributes on tip of chain - logger := testlog.Logger(t, log.LevelInfo) - eng := &testutils.MockEngine{} - ec := engine.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, sync.CLSync) - ah := NewAttributesHandler(logger, cfg, ec, eng) - - ec.SetUnsafeHead(refA0) - ec.SetSafeHead(refA0) - ec.SetFinalizedHead(refA0) - ec.SetPendingSafeL2Head(refA0) + l2 := &testutils.MockL2Client{} + emitter := &testutils.MockEmitter{} + ah := NewAttributesHandler(logger, cfg, context.Background(), l2, emitter)   - defer eng.AssertExpectations(t) + emitter.ExpectOnce(derive.ConfirmReceivedAttributesEvent{}) + emitter.ExpectOnce(engine.PendingSafeRequestEvent{}) + ah.OnEvent(derive.DerivedAttributesEvent{Attributes: attrA1Alt}) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes, "queued up derived attributes")   // sanity check test setup require.True(t, attrA1Alt.IsLastInSpan, "must be last in span for attributes to become safe")   - // process attrA1Alt on top - { - eng.ExpectForkchoiceUpdate(&eth.ForkchoiceState{ - HeadBlockHash: payloadA1Alt.ExecutionPayload.ParentHash, // reorg - SafeBlockHash: refA0.Hash, - FinalizedBlockHash: refA0.Hash, - }, attrA1Alt.Attributes, &eth.ForkchoiceUpdatedResult{ - PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionValid}, - PayloadID: &eth.PayloadID{1, 2, 3}, - }, nil) // to build the block - eng.ExpectGetPayload(eth.PayloadID{1, 2, 3}, payloadA1Alt, nil) - eng.ExpectNewPayload(payloadA1Alt.ExecutionPayload, payloadA1Alt.ParentBeaconBlockRoot, - &eth.PayloadStatusV1{Status: eth.ExecutionValid}, nil) // to persist the block - eng.ExpectForkchoiceUpdate(&eth.ForkchoiceState{ - HeadBlockHash: payloadA1Alt.ExecutionPayload.BlockHash, - SafeBlockHash: payloadA1Alt.ExecutionPayload.BlockHash, // it becomes safe - FinalizedBlockHash: refA0.Hash, - }, nil, &eth.ForkchoiceUpdatedResult{ - PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionValid}, - PayloadID: nil, - }, nil) // to make it canonical - } + // attrA1Alt will fit right on top of A0 + emitter.ExpectOnce(engine.ProcessAttributesEvent{Attributes: attrA1Alt}) + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA0, + Unsafe: refA0, + }) + l2.AssertExpectations(t) + emitter.AssertExpectations(t) + require.NotNil(t, ah.attributes)   - ah.SetAttributes(attrA1Alt) - - require.True(t, ah.HasAttributes()) - require.NoError(t, ah.Proceed(context.Background()), "insert new block") - require.False(t, ah.HasAttributes()) - - require.Equal(t, refA1Alt, ec.SafeL2Head(), "processing complete") + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA1Alt, + Unsafe: refA1Alt, + }) + emitter.AssertExpectations(t) + require.Nil(t, ah.attributes, "clear attributes after successful processing") })   t.Run("pending ahead of unsafe", func(t *testing.T) { // Legacy test case: if attributes fit on top of the pending safe block as expected, - // but if the unsafe block is older, then we can recover by updating the unsafe head. - + // but if the unsafe block is older, then we can recover by resetting. logger := testlog.Logger(t, log.LevelInfo) - eng := &testutils.MockEngine{} - ec := engine.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, sync.CLSync) - ah := NewAttributesHandler(logger, cfg, ec, eng) - - ec.SetUnsafeHead(refA0) - ec.SetSafeHead(refA0) - ec.SetFinalizedHead(refA0) - ec.SetPendingSafeL2Head(refA1) - - defer eng.AssertExpectations(t) - - ah.SetAttributes(attrA2) + l2 := &testutils.MockL2Client{} + emitter := &testutils.MockEmitter{} + ah := NewAttributesHandler(logger, cfg, context.Background(), l2, emitter)   - require.True(t, ah.HasAttributes()) - require.NoError(t, ah.Proceed(context.Background()), "detect unsafe - pending safe inconsistency") - require.True(t, ah.HasAttributes(), "still need the attributes, after unsafe head is corrected") - - require.Equal(t, refA0, ec.SafeL2Head(), "still same safe head") - require.Equal(t, refA1, ec.PendingSafeL2Head(), "still same pending safe head") - require.Equal(t, refA1, ec.UnsafeL2Head(), "updated unsafe head") + emitter.ExpectOnceType("ResetEvent") + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA1, + Unsafe: refA0, + }) + emitter.AssertExpectations(t) + l2.AssertExpectations(t) })   t.Run("no attributes", func(t *testing.T) { logger := testlog.Logger(t, log.LevelInfo) - eng := &testutils.MockEngine{} - ec := engine.NewEngineController(eng, logger, metrics.NoopMetrics, cfg, sync.CLSync) - ah := NewAttributesHandler(logger, cfg, ec, eng) - defer eng.AssertExpectations(t) + l2 := &testutils.MockL2Client{} + emitter := &testutils.MockEmitter{} + ah := NewAttributesHandler(logger, cfg, context.Background(), l2, emitter)   - require.Equal(t, ah.Proceed(context.Background()), io.EOF, "no attributes to process") + // If there are no attributes, we expect the pipeline to be requested to generate attributes. + emitter.ExpectOnce(derive.PipelineStepEvent{PendingSafe: refA1}) + ah.OnEvent(engine.PendingSafeUpdateEvent{ + PendingSafe: refA1, + Unsafe: refA1, + }) + // no calls to L2 or emitter when there is nothing to process + l2.AssertExpectations(t) + emitter.AssertExpectations(t) })   }
diff --git OP/op-node/rollup/clsync/clsync.go CELO/op-node/rollup/clsync/clsync.go index 989f1c7c98b6023fd09ec254ba750b7a91e7948a..faa4586105e30b924e046e7a1295bad96d534d15 100644 --- OP/op-node/rollup/clsync/clsync.go +++ CELO/op-node/rollup/clsync/clsync.go @@ -1,9 +1,7 @@ package clsync   import ( - "context" - "errors" - "io" + "sync"   "github.com/ethereum/go-ethereum/log"   @@ -20,27 +18,26 @@ type Metrics interface { RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) }   -type Engine interface { - engine.EngineState - InsertUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope, ref eth.L2BlockRef) error -} - // CLSync holds on to a queue of received unsafe payloads, // and tries to apply them to the tip of the chain when requested to. type CLSync struct { - log log.Logger - cfg *rollup.Config - metrics Metrics - ec Engine + log log.Logger + cfg *rollup.Config + metrics Metrics + + emitter rollup.EventEmitter + + mu sync.Mutex + unsafePayloads *PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps and duplicates }   -func NewCLSync(log log.Logger, cfg *rollup.Config, metrics Metrics, ec Engine) *CLSync { +func NewCLSync(log log.Logger, cfg *rollup.Config, metrics Metrics, emitter rollup.EventEmitter) *CLSync { return &CLSync{ log: log, cfg: cfg, metrics: metrics, - ec: ec, + emitter: emitter, unsafePayloads: NewPayloadsQueue(log, maxUnsafePayloadsMemory, payloadMemSize), } } @@ -58,67 +55,124 @@ } return ref }   -// AddUnsafePayload schedules an execution payload to be processed, ahead of deriving it from L1. -func (eq *CLSync) AddUnsafePayload(envelope *eth.ExecutionPayloadEnvelope) { - if envelope == nil { - eq.log.Warn("cannot add nil unsafe payload") - return +type ReceivedUnsafePayloadEvent struct { + Envelope *eth.ExecutionPayloadEnvelope +} + +func (ev ReceivedUnsafePayloadEvent) String() string { + return "received-unsafe-payload" +} + +func (eq *CLSync) OnEvent(ev rollup.Event) { + // Events may be concurrent in the future. Prevent unsafe concurrent modifications to the payloads queue. + eq.mu.Lock() + defer eq.mu.Unlock() + + switch x := ev.(type) { + case engine.InvalidPayloadEvent: + eq.onInvalidPayload(x) + case engine.ForkchoiceUpdateEvent: + eq.onForkchoiceUpdate(x) + case ReceivedUnsafePayloadEvent: + eq.onUnsafePayload(x) } +}   - if err := eq.unsafePayloads.Push(envelope); err != nil { - eq.log.Warn("Could not add unsafe payload", "id", envelope.ExecutionPayload.ID(), "timestamp", uint64(envelope.ExecutionPayload.Timestamp), "err", err) - return +// onInvalidPayload checks if the first next-up payload matches the invalid payload. +// If so, the payload is dropped, to give the next payloads a try. +func (eq *CLSync) onInvalidPayload(x engine.InvalidPayloadEvent) { + eq.log.Debug("CL sync received invalid-payload report", x.Envelope.ExecutionPayload.ID()) + + block := x.Envelope.ExecutionPayload + if peek := eq.unsafePayloads.Peek(); peek != nil && + block.BlockHash == peek.ExecutionPayload.BlockHash { + eq.log.Warn("Dropping invalid unsafe payload", + "hash", block.BlockHash, "number", uint64(block.BlockNumber), + "timestamp", uint64(block.Timestamp)) + eq.unsafePayloads.Pop() } - p := eq.unsafePayloads.Peek() - eq.metrics.RecordUnsafePayloadsBuffer(uint64(eq.unsafePayloads.Len()), eq.unsafePayloads.MemSize(), p.ExecutionPayload.ID()) - eq.log.Trace("Next unsafe payload to process", "next", p.ExecutionPayload.ID(), "timestamp", uint64(p.ExecutionPayload.Timestamp)) }   -// Proceed dequeues the next applicable unsafe payload, if any, to apply to the tip of the chain. -// EOF error means we can't process the next unsafe payload. The caller should then try a different form of syncing. -func (eq *CLSync) Proceed(ctx context.Context) error { +// onForkchoiceUpdate peeks at the next applicable unsafe payload, if any, +// to apply on top of the received forkchoice pre-state. +// The payload is held on to until the forkchoice changes (success case) or the payload is reported to be invalid. +func (eq *CLSync) onForkchoiceUpdate(x engine.ForkchoiceUpdateEvent) { + eq.log.Debug("CL sync received forkchoice update", + "unsafe", x.UnsafeL2Head, "safe", x.SafeL2Head, "finalized", x.FinalizedL2Head) + + for { + pop, abort := eq.fromQueue(x) + if abort { + return + } + if pop { + eq.unsafePayloads.Pop() + } else { + break + } + } + + firstEnvelope := eq.unsafePayloads.Peek() + + // We don't pop from the queue. If there is a temporary error then we can retry. + // Upon next forkchoice update or invalid-payload event we can remove it from the queue. + eq.emitter.Emit(engine.ProcessUnsafePayloadEvent{Envelope: firstEnvelope}) +} + +// fromQueue determines what to do with the tip of the payloads-queue, given the forkchoice pre-state. +// If abort, there is nothing to process (either due to empty queue, or unsuitable tip). +// If pop, the tip should be dropped, and processing can repeat from there. +// If not abort or pop, the tip is ready to process. +func (eq *CLSync) fromQueue(x engine.ForkchoiceUpdateEvent) (pop bool, abort bool) { if eq.unsafePayloads.Len() == 0 { - return io.EOF + return false, true } firstEnvelope := eq.unsafePayloads.Peek() first := firstEnvelope.ExecutionPayload   - if uint64(first.BlockNumber) <= eq.ec.SafeL2Head().Number { - eq.log.Info("skipping unsafe payload, since it is older than safe head", "safe", eq.ec.SafeL2Head().ID(), "unsafe", eq.ec.UnsafeL2Head().ID(), "unsafe_payload", first.ID()) - eq.unsafePayloads.Pop() - return nil + if first.BlockHash == x.UnsafeL2Head.Hash { + eq.log.Debug("successfully processed payload, removing it from the payloads queue now") + return true, false + } + + if uint64(first.BlockNumber) <= x.SafeL2Head.Number { + eq.log.Info("skipping unsafe payload, since it is older than safe head", "safe", x.SafeL2Head.ID(), "unsafe", x.UnsafeL2Head.ID(), "unsafe_payload", first.ID()) + return true, false } - if uint64(first.BlockNumber) <= eq.ec.UnsafeL2Head().Number { - eq.log.Info("skipping unsafe payload, since it is older than unsafe head", "unsafe", eq.ec.UnsafeL2Head().ID(), "unsafe_payload", first.ID()) - eq.unsafePayloads.Pop() - return nil + if uint64(first.BlockNumber) <= x.UnsafeL2Head.Number { + eq.log.Info("skipping unsafe payload, since it is older than unsafe head", "unsafe", x.UnsafeL2Head.ID(), "unsafe_payload", first.ID()) + return true, false }   // Ensure that the unsafe payload builds upon the current unsafe head - if first.ParentHash != eq.ec.UnsafeL2Head().Hash { - if uint64(first.BlockNumber) == eq.ec.UnsafeL2Head().Number+1 { - eq.log.Info("skipping unsafe payload, since it does not build onto the existing unsafe chain", "safe", eq.ec.SafeL2Head().ID(), "unsafe", eq.ec.UnsafeL2Head().ID(), "unsafe_payload", first.ID()) - eq.unsafePayloads.Pop() + if first.ParentHash != x.UnsafeL2Head.Hash { + if uint64(first.BlockNumber) == x.UnsafeL2Head.Number+1 { + eq.log.Info("skipping unsafe payload, since it does not build onto the existing unsafe chain", "safe", x.SafeL2Head.ID(), "unsafe", x.UnsafeL2Head.ID(), "unsafe_payload", first.ID()) + return true, false } - return io.EOF // time to go to next stage if we cannot process the first unsafe payload + return false, true // rollup-node should try something different if it cannot process the first unsafe payload }   - ref, err := derive.PayloadToBlockRef(eq.cfg, first) - if err != nil { - eq.log.Error("failed to decode L2 block ref from payload", "err", err) - eq.unsafePayloads.Pop() - return nil + return false, false +} + +// AddUnsafePayload schedules an execution payload to be processed, ahead of deriving it from L1. +func (eq *CLSync) onUnsafePayload(x ReceivedUnsafePayloadEvent) { + eq.log.Debug("CL sync received payload", "payload", x.Envelope.ExecutionPayload.ID()) + envelope := x.Envelope + if envelope == nil { + eq.log.Warn("cannot add nil unsafe payload") + return }   - if err := eq.ec.InsertUnsafePayload(ctx, firstEnvelope, ref); errors.Is(err, derive.ErrTemporary) { - eq.log.Debug("Temporary error while inserting unsafe payload", "hash", ref.Hash, "number", ref.Number, "timestamp", ref.Time, "l1Origin", ref.L1Origin) - return err - } else if err != nil { - eq.log.Warn("Dropping invalid unsafe payload", "hash", ref.Hash, "number", ref.Number, "timestamp", ref.Time, "l1Origin", ref.L1Origin) - eq.unsafePayloads.Pop() - return err + if err := eq.unsafePayloads.Push(envelope); err != nil { + eq.log.Warn("Could not add unsafe payload", "id", envelope.ExecutionPayload.ID(), "timestamp", uint64(envelope.ExecutionPayload.Timestamp), "err", err) + return } - eq.unsafePayloads.Pop() - eq.log.Trace("Executed unsafe payload", "hash", ref.Hash, "number", ref.Number, "timestamp", ref.Time, "l1Origin", ref.L1Origin) - return nil + p := eq.unsafePayloads.Peek() + eq.metrics.RecordUnsafePayloadsBuffer(uint64(eq.unsafePayloads.Len()), eq.unsafePayloads.MemSize(), p.ExecutionPayload.ID()) + eq.log.Trace("Next unsafe payload to process", "next", p.ExecutionPayload.ID(), "timestamp", uint64(p.ExecutionPayload.Timestamp)) + + // request forkchoice signal, so we can process the payload maybe + eq.emitter.Emit(engine.ForkchoiceRequestEvent{}) }
diff --git OP/op-node/rollup/clsync/clsync_test.go CELO/op-node/rollup/clsync/clsync_test.go index 67bcc25f82eaa8416d6fc45a9a4ae09634dffe12..f42c67f9220e58556d13b6e924982191ed11d592 100644 --- OP/op-node/rollup/clsync/clsync_test.go +++ CELO/op-node/rollup/clsync/clsync_test.go @@ -1,9 +1,6 @@ package clsync   import ( - "context" - "errors" - "io" "math/big" "math/rand" // nosemgrep "testing" @@ -17,38 +14,11 @@ "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testutils" ) - -type fakeEngine struct { - unsafe, safe, finalized eth.L2BlockRef - - err error -} - -func (f *fakeEngine) Finalized() eth.L2BlockRef { - return f.finalized -} - -func (f *fakeEngine) UnsafeL2Head() eth.L2BlockRef { - return f.unsafe -} - -func (f *fakeEngine) SafeL2Head() eth.L2BlockRef { - return f.safe -} - -func (f *fakeEngine) InsertUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope, ref eth.L2BlockRef) error { - if f.err != nil { - return f.err - } - f.unsafe = ref - return nil -} - -var _ Engine = (*fakeEngine)(nil)   func TestCLSync(t *testing.T) { rng := rand.New(rand.NewSource(1234)) @@ -155,157 +125,252 @@ // When a previously received unsafe block is older than the tip of the chain, we want to drop it. t.Run("drop old", func(t *testing.T) { logger := testlog.Logger(t, log.LevelError) - eng := &fakeEngine{ - unsafe: refA2, - safe: refA0, - finalized: refA0, - } - cl := NewCLSync(logger, cfg, metrics, eng)   - cl.AddUnsafePayload(payloadA1) - require.NoError(t, cl.Proceed(context.Background())) + emitter := &testutils.MockEmitter{} + cl := NewCLSync(logger, cfg, metrics, emitter) + + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA1}) + emitter.AssertExpectations(t) + + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA2, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) // no new events expected to be emitted   require.Nil(t, cl.unsafePayloads.Peek(), "pop because too old") - require.Equal(t, refA2, eng.unsafe, "keep unsafe head") })   // When we already have the exact payload as tip, then no need to process it t.Run("drop equal", func(t *testing.T) { logger := testlog.Logger(t, log.LevelError) - eng := &fakeEngine{ - unsafe: refA1, - safe: refA0, - finalized: refA0, - } - cl := NewCLSync(logger, cfg, metrics, eng)   - cl.AddUnsafePayload(payloadA1) - require.NoError(t, cl.Proceed(context.Background())) + emitter := &testutils.MockEmitter{} + cl := NewCLSync(logger, cfg, metrics, emitter) + + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA1}) + emitter.AssertExpectations(t) + + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA1, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) // no new events expected to be emitted   require.Nil(t, cl.unsafePayloads.Peek(), "pop because seen") - require.Equal(t, refA1, eng.unsafe, "keep unsafe head") })   // When we have a different payload, at the same height, then we want to keep it. // The unsafe chain consensus preserves the first-seen payload. t.Run("ignore conflict", func(t *testing.T) { logger := testlog.Logger(t, log.LevelError) - eng := &fakeEngine{ - unsafe: altRefA1, - safe: refA0, - finalized: refA0, - } - cl := NewCLSync(logger, cfg, metrics, eng) + + emitter := &testutils.MockEmitter{} + cl := NewCLSync(logger, cfg, metrics, emitter)   - cl.AddUnsafePayload(payloadA1) - require.NoError(t, cl.Proceed(context.Background())) + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA1}) + emitter.AssertExpectations(t) + + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: altRefA1, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) // no new events expected to be emitted   require.Nil(t, cl.unsafePayloads.Peek(), "pop because alternative") - require.Equal(t, altRefA1, eng.unsafe, "keep unsafe head") })   t.Run("ignore unsafe reorg", func(t *testing.T) { logger := testlog.Logger(t, log.LevelError) - eng := &fakeEngine{ - unsafe: altRefA1, - safe: refA0, - finalized: refA0, - } - cl := NewCLSync(logger, cfg, metrics, eng) + + emitter := &testutils.MockEmitter{} + cl := NewCLSync(logger, cfg, metrics, emitter) + + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA2}) + emitter.AssertExpectations(t)   - cl.AddUnsafePayload(payloadA2) - require.ErrorIs(t, cl.Proceed(context.Background()), io.EOF, "payload2 does not fit onto alt1, thus retrieve next input from L1") + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: altRefA1, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) // no new events expected, since A2 does not fit onto altA1   require.Nil(t, cl.unsafePayloads.Peek(), "pop because not applicable") - require.Equal(t, altRefA1, eng.unsafe, "keep unsafe head") })   t.Run("success", func(t *testing.T) { logger := testlog.Logger(t, log.LevelError) - eng := &fakeEngine{ - unsafe: refA0, - safe: refA0, - finalized: refA0, - } - cl := NewCLSync(logger, cfg, metrics, eng) + + emitter := &testutils.MockEmitter{} + cl := NewCLSync(logger, cfg, metrics, emitter) + emitter.AssertExpectations(t) // nothing to process yet   - require.ErrorIs(t, cl.Proceed(context.Background()), io.EOF, "nothing to process yet") require.Nil(t, cl.unsafePayloads.Peek(), "no payloads yet")   - cl.AddUnsafePayload(payloadA1) + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA1}) + emitter.AssertExpectations(t) + lowest := cl.LowestQueuedUnsafeBlock() require.Equal(t, refA1, lowest, "expecting A1 next") - require.NoError(t, cl.Proceed(context.Background())) + + // payload A1 should be possible to process on top of A0 + emitter.ExpectOnce(engine.ProcessUnsafePayloadEvent{Envelope: payloadA1}) + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA0, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) + + // now pretend the payload was processed: we can drop A1 now + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA1, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) require.Nil(t, cl.unsafePayloads.Peek(), "pop because applied") - require.Equal(t, refA1, eng.unsafe, "new unsafe head")   - cl.AddUnsafePayload(payloadA2) + // repeat for A2 + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA2}) + emitter.AssertExpectations(t) + lowest = cl.LowestQueuedUnsafeBlock() require.Equal(t, refA2, lowest, "expecting A2 next") - require.NoError(t, cl.Proceed(context.Background())) + + emitter.ExpectOnce(engine.ProcessUnsafePayloadEvent{Envelope: payloadA2}) + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA1, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) + + // now pretend the payload was processed: we can drop A2 now + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA2, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) require.Nil(t, cl.unsafePayloads.Peek(), "pop because applied") - require.Equal(t, refA2, eng.unsafe, "new unsafe head") })   t.Run("double buffer", func(t *testing.T) { logger := testlog.Logger(t, log.LevelError) - eng := &fakeEngine{ - unsafe: refA0, - safe: refA0, - finalized: refA0, - } - cl := NewCLSync(logger, cfg, metrics, eng) + + emitter := &testutils.MockEmitter{} + cl := NewCLSync(logger, cfg, metrics, emitter)   - cl.AddUnsafePayload(payloadA1) - cl.AddUnsafePayload(payloadA2) + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA1}) + emitter.AssertExpectations(t) + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA2}) + emitter.AssertExpectations(t)   lowest := cl.LowestQueuedUnsafeBlock() require.Equal(t, refA1, lowest, "expecting A1 next")   - require.NoError(t, cl.Proceed(context.Background())) - require.NotNil(t, cl.unsafePayloads.Peek(), "next is ready") - require.Equal(t, refA1, eng.unsafe, "new unsafe head") - require.NoError(t, cl.Proceed(context.Background())) + emitter.ExpectOnce(engine.ProcessUnsafePayloadEvent{Envelope: payloadA1}) + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA0, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) + require.Equal(t, 2, cl.unsafePayloads.Len(), "still holding on to A1, and queued A2") + + // Now pretend the payload was processed: we can drop A1 now. + // The CL-sync will try to immediately continue with A2. + emitter.ExpectOnce(engine.ProcessUnsafePayloadEvent{Envelope: payloadA2}) + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA1, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) + + // now pretend the payload was processed: we can drop A2 now + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA2, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) require.Nil(t, cl.unsafePayloads.Peek(), "done") - require.Equal(t, refA2, eng.unsafe, "new unsafe head") })   t.Run("temporary error", func(t *testing.T) { logger := testlog.Logger(t, log.LevelError) - eng := &fakeEngine{ - unsafe: refA0, - safe: refA0, - finalized: refA0, - } - cl := NewCLSync(logger, cfg, metrics, eng) + + emitter := &testutils.MockEmitter{} + cl := NewCLSync(logger, cfg, metrics, emitter) + + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA1}) + emitter.AssertExpectations(t)   - testErr := derive.NewTemporaryError(errors.New("test error")) - eng.err = testErr - cl.AddUnsafePayload(payloadA1) - require.ErrorIs(t, cl.Proceed(context.Background()), testErr) - require.Equal(t, refA0, eng.unsafe, "old unsafe head after error") + emitter.ExpectOnce(engine.ProcessUnsafePayloadEvent{Envelope: payloadA1}) + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA0, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) + + // On temporary errors we don't need any feedback from the engine. + // We just hold on to what payloads there are in the queue. require.NotNil(t, cl.unsafePayloads.Peek(), "no pop because temporary error")   - eng.err = nil - require.NoError(t, cl.Proceed(context.Background())) - require.Equal(t, refA1, eng.unsafe, "new unsafe head after resolved error") + // Pretend we are still stuck on the same forkchoice. The CL-sync will retry sneding the payload. + emitter.ExpectOnce(engine.ProcessUnsafePayloadEvent{Envelope: payloadA1}) + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA0, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t) + require.NotNil(t, cl.unsafePayloads.Peek(), "no pop because retry still unconfirmed") + + // Now confirm we got the payload this time + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA1, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) require.Nil(t, cl.unsafePayloads.Peek(), "pop because valid") })   t.Run("invalid payload error", func(t *testing.T) { logger := testlog.Logger(t, log.LevelError) - eng := &fakeEngine{ - unsafe: refA0, - safe: refA0, - finalized: refA0, - } - cl := NewCLSync(logger, cfg, metrics, eng) + emitter := &testutils.MockEmitter{} + cl := NewCLSync(logger, cfg, metrics, emitter) + + // CLSync gets payload and requests engine state, to later determine if payload should be forwarded + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + cl.OnEvent(ReceivedUnsafePayloadEvent{Envelope: payloadA1}) + emitter.AssertExpectations(t) + + // Engine signals, CLSync sends the payload + emitter.ExpectOnce(engine.ProcessUnsafePayloadEvent{Envelope: payloadA1}) + cl.OnEvent(engine.ForkchoiceUpdateEvent{ + UnsafeL2Head: refA0, + SafeL2Head: refA0, + FinalizedL2Head: refA0, + }) + emitter.AssertExpectations(t)   - testErr := errors.New("test error") - eng.err = testErr - cl.AddUnsafePayload(payloadA1) - require.ErrorIs(t, cl.Proceed(context.Background()), testErr) - require.Equal(t, refA0, eng.unsafe, "old unsafe head after error") + // Pretend the payload is bad. It should not be retried after this. + cl.OnEvent(engine.InvalidPayloadEvent{Envelope: payloadA1}) + emitter.AssertExpectations(t) require.Nil(t, cl.unsafePayloads.Peek(), "pop because invalid") }) }
diff --git OP/op-node/rollup/conductor/conductor.go CELO/op-node/rollup/conductor/conductor.go index 912b08cf071e2c954b686cc31e5ffbf52287fa8a..927d88035ccb146d571f100242ece76da41f402b 100644 --- OP/op-node/rollup/conductor/conductor.go +++ CELO/op-node/rollup/conductor/conductor.go @@ -9,14 +9,21 @@ // SequencerConductor is an interface for the driver to communicate with the sequencer conductor. // It is used to determine if the current node is the active sequencer, and to commit unsafe payloads to the conductor log. type SequencerConductor interface { + // Leader returns true if this node is the leader sequencer. Leader(ctx context.Context) (bool, error) + // CommitUnsafePayload commits an unsafe payload to the conductor FSM. CommitUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error + // OverrideLeader forces current node to be considered leader and be able to start sequencing during disaster situations in HA mode. + OverrideLeader(ctx context.Context) error + // Close closes the conductor client. Close() }   // NoOpConductor is a no-op conductor that assumes this node is the leader sequencer. type NoOpConductor struct{}   +var _ SequencerConductor = &NoOpConductor{} + // Leader returns true if this node is the leader sequencer. NoOpConductor always returns true. func (c *NoOpConductor) Leader(ctx context.Context) (bool, error) { return true, nil @@ -24,6 +31,11 @@ }   // CommitUnsafePayload commits an unsafe payload to the conductor log. func (c *NoOpConductor) CommitUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error { + return nil +} + +// OverrideLeader implements SequencerConductor. +func (c *NoOpConductor) OverrideLeader(ctx context.Context) error { return nil }
diff --git OP/op-node/rollup/derive/channel_test.go CELO/op-node/rollup/derive/channel_test.go index e853d622aa04ddf5f080a5867f2a81115e6ad05e..a6594492837b0e8d426d82c338bce1a848b9ebcb 100644 --- OP/op-node/rollup/derive/channel_test.go +++ CELO/op-node/rollup/derive/channel_test.go @@ -7,9 +7,9 @@ "math/big" "math/rand" "testing"   - "github.com/DataDog/zstd" "github.com/andybalholm/brotli" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/klauspost/compress/zstd" "github.com/stretchr/testify/require" )   @@ -143,8 +143,9 @@ } case ca == Zstd: // invalid algo return func(buf *bytes.Buffer, t *testing.T) { buf.WriteByte(0x02) // invalid channel version byte - writer := zstd.NewWriter(buf) - _, err := writer.Write(encodedBatch.Bytes()) + writer, err := zstd.NewWriter(buf) + require.NoError(t, err) + _, err = writer.Write(encodedBatch.Bytes()) require.NoError(t, err) require.NoError(t, writer.Close()) }
diff --git OP/op-node/rollup/derive/data_source.go CELO/op-node/rollup/derive/data_source.go index 5f2e48199bf3704b229c4fb0549e92b521c892d3..39b62de2e54f0888ca30c6341d3c5aa49d2d0463 100644 --- OP/op-node/rollup/derive/data_source.go +++ CELO/op-node/rollup/derive/data_source.go @@ -28,7 +28,7 @@ }   type PlasmaInputFetcher interface { // GetInput fetches the input for the given commitment at the given block number from the DA storage service. - GetInput(ctx context.Context, l1 plasma.L1Fetcher, c plasma.CommitmentData, blockId eth.BlockID) (eth.Data, error) + GetInput(ctx context.Context, l1 plasma.L1Fetcher, c plasma.CommitmentData, blockId eth.L1BlockRef) (eth.Data, error) // AdvanceL1Origin advances the L1 origin to the given block number, syncing the DA challenge events. AdvanceL1Origin(ctx context.Context, l1 plasma.L1Fetcher, blockId eth.BlockID) error // Reset the challenge origin in case of L1 reorg @@ -78,7 +78,7 @@ src = NewCalldataSource(ctx, ds.log, ds.dsCfg, ds.fetcher, ref, batcherAddr) } if ds.dsCfg.plasmaEnabled { // plasma([calldata | blobdata](l1Ref)) -> data - return NewPlasmaDataSource(ds.log, src, ds.fetcher, ds.plasmaFetcher, ref.ID()), nil + return NewPlasmaDataSource(ds.log, src, ds.fetcher, ds.plasmaFetcher, ref), nil } return src, nil }
diff --git OP/op-node/rollup/derive/deriver.go CELO/op-node/rollup/derive/deriver.go new file mode 100644 index 0000000000000000000000000000000000000000..da3a71577725c19954617ac3074e1e77cd0f1331 --- /dev/null +++ CELO/op-node/rollup/derive/deriver.go @@ -0,0 +1,118 @@ +package derive + +import ( + "context" + "errors" + "io" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type DeriverIdleEvent struct { + Origin eth.L1BlockRef +} + +func (d DeriverIdleEvent) String() string { + return "derivation-idle" +} + +type DeriverMoreEvent struct{} + +func (d DeriverMoreEvent) String() string { + return "deriver-more" +} + +// ConfirmReceivedAttributesEvent signals that the derivation pipeline may generate new attributes. +// After emitting DerivedAttributesEvent, no new attributes will be generated until a confirmation of reception. +type ConfirmReceivedAttributesEvent struct{} + +func (d ConfirmReceivedAttributesEvent) String() string { + return "confirm-received-attributes" +} + +type ConfirmPipelineResetEvent struct{} + +func (d ConfirmPipelineResetEvent) String() string { + return "confirm-pipeline-reset" +} + +// DerivedAttributesEvent is emitted when new attributes are available to apply to the engine. +type DerivedAttributesEvent struct { + Attributes *AttributesWithParent +} + +func (ev DerivedAttributesEvent) String() string { + return "derived-attributes" +} + +type PipelineStepEvent struct { + PendingSafe eth.L2BlockRef +} + +func (ev PipelineStepEvent) String() string { + return "pipeline-step" +} + +type PipelineDeriver struct { + pipeline *DerivationPipeline + + ctx context.Context + + emitter rollup.EventEmitter + + needAttributesConfirmation bool +} + +func NewPipelineDeriver(ctx context.Context, pipeline *DerivationPipeline, emitter rollup.EventEmitter) *PipelineDeriver { + return &PipelineDeriver{ + pipeline: pipeline, + ctx: ctx, + emitter: emitter, + } +} + +func (d *PipelineDeriver) OnEvent(ev rollup.Event) { + switch x := ev.(type) { + case rollup.ResetEvent: + d.pipeline.Reset() + case PipelineStepEvent: + // Don't generate attributes if there are already attributes in-flight + if d.needAttributesConfirmation { + d.pipeline.log.Debug("Previously sent attributes are unconfirmed to be received") + return + } + d.pipeline.log.Trace("Derivation pipeline step", "onto_origin", d.pipeline.Origin()) + attrib, err := d.pipeline.Step(d.ctx, x.PendingSafe) + if err == io.EOF { + d.pipeline.log.Debug("Derivation process went idle", "progress", d.pipeline.Origin(), "err", err) + d.emitter.Emit(DeriverIdleEvent{Origin: d.pipeline.Origin()}) + } else if err != nil && errors.Is(err, EngineELSyncing) { + d.pipeline.log.Debug("Derivation process went idle because the engine is syncing", "progress", d.pipeline.Origin(), "err", err) + d.emitter.Emit(DeriverIdleEvent{Origin: d.pipeline.Origin()}) + } else if err != nil && errors.Is(err, ErrReset) { + d.emitter.Emit(rollup.ResetEvent{Err: err}) + } else if err != nil && errors.Is(err, ErrTemporary) { + d.emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: err}) + } else if err != nil && errors.Is(err, ErrCritical) { + d.emitter.Emit(rollup.CriticalErrorEvent{Err: err}) + } else if err != nil && errors.Is(err, NotEnoughData) { + // don't do a backoff for this error + d.emitter.Emit(DeriverMoreEvent{}) + } else if err != nil { + d.pipeline.log.Error("Derivation process error", "err", err) + d.emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: err}) + } else { + if attrib != nil { + d.needAttributesConfirmation = true + d.emitter.Emit(DerivedAttributesEvent{Attributes: attrib}) + } else { + d.emitter.Emit(DeriverMoreEvent{}) // continue with the next step if we can + } + } + case ConfirmPipelineResetEvent: + d.pipeline.ConfirmEngineReset() + case ConfirmReceivedAttributesEvent: + d.needAttributesConfirmation = false + } +}
diff --git OP/op-node/rollup/derive/pipeline.go CELO/op-node/rollup/derive/pipeline.go index 31295870f40c9df5bbb620d01a6455e5e7190695..7d74fb9da800c19fb3473605953cb0042fe38bc5 100644 --- OP/op-node/rollup/derive/pipeline.go +++ CELO/op-node/rollup/derive/pipeline.go @@ -22,6 +22,7 @@ RecordChannelTimedOut() RecordFrame() RecordDerivedBatches(batchType string) SetDerivationIdle(idle bool) + RecordPipelineReset() }   type L1Fetcher interface { @@ -194,6 +195,8 @@ // initialReset does the initial reset work of finding the L1 point to rewind back to func (dp *DerivationPipeline) initialReset(ctx context.Context, resetL2Safe eth.L2BlockRef) error { dp.log.Info("Rewinding derivation-pipeline L1 traversal to handle reset") + + dp.metrics.RecordPipelineReset()   // Walk back L2 chain to find the L1 origin that is old enough to start buffering channel data from. pipelineL2 := resetL2Safe
diff --git OP/op-node/rollup/derive/plasma_data_source.go CELO/op-node/rollup/derive/plasma_data_source.go index 9db4dd1cc55371e6475540106e7186fc6668326b..19beb145999b60c7b597166c7f1b3ec8dc07eded 100644 --- OP/op-node/rollup/derive/plasma_data_source.go +++ CELO/op-node/rollup/derive/plasma_data_source.go @@ -17,12 +17,12 @@ log log.Logger src DataIter fetcher PlasmaInputFetcher l1 L1Fetcher - id eth.BlockID + id eth.L1BlockRef // keep track of a pending commitment so we can keep trying to fetch the input. comm plasma.CommitmentData }   -func NewPlasmaDataSource(log log.Logger, src DataIter, l1 L1Fetcher, fetcher PlasmaInputFetcher, id eth.BlockID) *PlasmaDataSource { +func NewPlasmaDataSource(log log.Logger, src DataIter, l1 L1Fetcher, fetcher PlasmaInputFetcher, id eth.L1BlockRef) *PlasmaDataSource { return &PlasmaDataSource{ log: log, src: src, @@ -37,7 +37,7 @@ // Process origin syncs the challenge contract events and updates the local challenge states // before we can proceed to fetch the input data. This function can be called multiple times // for the same origin and noop if the origin was already processed. It is also called if // there is not commitment in the current origin. - if err := s.fetcher.AdvanceL1Origin(ctx, s.l1, s.id); err != nil { + if err := s.fetcher.AdvanceL1Origin(ctx, s.l1, s.id.ID()); err != nil { if errors.Is(err, plasma.ErrReorgRequired) { return nil, NewResetError(fmt.Errorf("new expired challenge")) } @@ -83,13 +83,13 @@ s.comm = nil // skip the input return s.Next(ctx) } else if errors.Is(err, plasma.ErrMissingPastWindow) { - return nil, NewCriticalError(fmt.Errorf("data for comm %x not available: %w", s.comm, err)) + return nil, NewCriticalError(fmt.Errorf("data for comm %s not available: %w", s.comm, err)) } else if errors.Is(err, plasma.ErrPendingChallenge) { // continue stepping without slowing down. return nil, NotEnoughData } else if err != nil { // return temporary error so we can keep retrying. - return nil, NewTemporaryError(fmt.Errorf("failed to fetch input data with comm %x from da service: %w", s.comm, err)) + return nil, NewTemporaryError(fmt.Errorf("failed to fetch input data with comm %s from da service: %w", s.comm, err)) } // inputs are limited to a max size to ensure they can be challenged in the DA contract. if s.comm.CommitmentType() == plasma.Keccak256CommitmentType && len(data) > plasma.MaxInputSize {
diff --git OP/op-node/rollup/derive/plasma_data_source_test.go CELO/op-node/rollup/derive/plasma_data_source_test.go index ae13be8e37f877ca6e3043d8d8a71781ca71af13..606f754f9930f9969721eeb22b23c6b428219d77 100644 --- OP/op-node/rollup/derive/plasma_data_source_test.go +++ CELO/op-node/rollup/derive/plasma_data_source_test.go @@ -56,7 +56,7 @@ ChallengeWindow: 90, ResolveWindow: 90, } metrics := &plasma.NoopMetrics{}   - daState := plasma.NewState(logger, metrics) + daState := plasma.NewState(logger, metrics, pcfg)   da := plasma.NewPlasmaDAWithState(logger, pcfg, storage, metrics, daState)   @@ -97,6 +97,7 @@ } // keep track of random input data to validate against var inputs [][]byte var comms []plasma.CommitmentData + var inclusionBlocks []eth.L1BlockRef   signer := cfg.L1Signer()   @@ -131,6 +132,7 @@ // plasma da tests are designed for keccak256 commitments, so we type assert here kComm := comm.(plasma.Keccak256Commitment) inputs = append(inputs, input) comms = append(comms, kComm) + inclusionBlocks = append(inclusionBlocks, ref)   tx, err := types.SignNewTx(batcherPriv, signer, &types.DynamicFeeTx{ ChainID: signer.ChainID(), @@ -161,7 +163,7 @@ // challenge the first 4 commitments as soon as we have collected them all if len(comms) >= 4 && nc < 7 { // skip a block between each challenge transaction if nc%2 == 0 { - daState.SetActiveChallenge(comms[nc/2].Encode(), ref.Number, pcfg.ResolveWindow) + daState.CreateChallenge(comms[nc/2], ref.ID(), inclusionBlocks[nc/2].Number) logger.Info("setting active challenge", "comm", comms[nc/2]) } nc++ @@ -275,11 +277,9 @@ }   }   - // trigger l1 finalization signal - da.Finalize(l1Refs[len(l1Refs)-32]) - + // finalize based on the second to last block, which will prune the commitment on block 2, and make it finalized + da.Finalize(l1Refs[len(l1Refs)-2]) finalitySignal.AssertExpectations(t) - l1F.AssertExpectations(t) }   // This tests makes sure the pipeline returns a temporary error if data is not found. @@ -299,7 +299,7 @@ }   metrics := &plasma.NoopMetrics{}   - daState := plasma.NewState(logger, metrics) + daState := plasma.NewState(logger, metrics, pcfg)   da := plasma.NewPlasmaDAWithState(logger, pcfg, storage, metrics, daState)   @@ -396,8 +396,11 @@ // not enough data _, err = src.Next(ctx) require.ErrorIs(t, err, NotEnoughData)   + // create and resolve a challenge + daState.CreateChallenge(comm, ref.ID(), ref.Number) // now challenge is resolved - daState.SetResolvedChallenge(comm.Encode(), input, ref.Number+2) + err = daState.ResolveChallenge(comm, eth.BlockID{Number: ref.Number + 2}, ref.Number, input) + require.NoError(t, err)   // derivation can resume data, err := src.Next(ctx)
diff --git OP/op-node/rollup/driver/driver.go CELO/op-node/rollup/driver/driver.go index 7213eaf98bac822e4b6620b993b221b6bfd5795e..d65f996c4e5be75abaec62f16f4fe0508183248d 100644 --- OP/op-node/rollup/driver/driver.go +++ CELO/op-node/rollup/driver/driver.go @@ -77,8 +77,6 @@ }   type CLSync interface { LowestQueuedUnsafeBlock() eth.L2BlockRef - AddUnsafePayload(payload *eth.ExecutionPayloadEnvelope) - Proceed(ctx context.Context) error }   type AttributesHandler interface { @@ -93,9 +91,8 @@ Proceed(ctx context.Context) error }   type Finalizer interface { - Finalize(ctx context.Context, ref eth.L1BlockRef) FinalizedL1() eth.L1BlockRef - engine.FinalizerHooks + rollup.Deriver }   type PlasmaIface interface { @@ -173,53 +170,68 @@ syncCfg *sync.Config, sequencerConductor conductor.SequencerConductor, plasma PlasmaIface, ) *Driver { + driverCtx, driverCancel := context.WithCancel(context.Background()) + rootDeriver := &rollup.SynchronousDerivers{} + synchronousEvents := rollup.NewSynchronousEvents(log, driverCtx, rootDeriver) + l1 = NewMeteredL1Fetcher(l1, metrics) l1State := NewL1State(log, metrics) sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1) findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth) verifConfDepth := NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1) - engine := engine.NewEngineController(l2, log, metrics, cfg, syncCfg.SyncMode) - clSync := clsync.NewCLSync(log, cfg, metrics, engine) + ec := engine.NewEngineController(l2, log, metrics, cfg, syncCfg.SyncMode, synchronousEvents) + engineResetDeriver := engine.NewEngineResetDeriver(driverCtx, log, cfg, l1, l2, syncCfg, synchronousEvents) + clSync := clsync.NewCLSync(log, cfg, metrics, synchronousEvents)   var finalizer Finalizer if cfg.PlasmaEnabled() { - finalizer = finality.NewPlasmaFinalizer(log, cfg, l1, engine, plasma) + finalizer = finality.NewPlasmaFinalizer(driverCtx, log, cfg, l1, synchronousEvents, plasma) } else { - finalizer = finality.NewFinalizer(log, cfg, l1, engine) + finalizer = finality.NewFinalizer(driverCtx, log, cfg, l1, synchronousEvents) }   - attributesHandler := attributes.NewAttributesHandler(log, cfg, engine, l2) + attributesHandler := attributes.NewAttributesHandler(log, cfg, driverCtx, l2, synchronousEvents) derivationPipeline := derive.NewDerivationPipeline(log, cfg, verifConfDepth, l1Blobs, plasma, l2, metrics) + pipelineDeriver := derive.NewPipelineDeriver(driverCtx, derivationPipeline, synchronousEvents) attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, l2) - meteredEngine := NewMeteredEngine(cfg, engine, metrics, log) // Only use the metered engine in the sequencer b/c it records sequencing metrics. + meteredEngine := NewMeteredEngine(cfg, ec, metrics, log) // Only use the metered engine in the sequencer b/c it records sequencing metrics. sequencer := NewSequencer(log, cfg, meteredEngine, attrBuilder, findL1Origin, metrics) - driverCtx, driverCancel := context.WithCancel(context.Background()) asyncGossiper := async.NewAsyncGossiper(driverCtx, network, log, metrics) - return &Driver{ - l1State: l1State, - SyncDeriver: &SyncDeriver{ - Derivation: derivationPipeline, - Finalizer: finalizer, - AttributesHandler: attributesHandler, - SafeHeadNotifs: safeHeadListener, - CLSync: clSync, - Engine: engine, - }, + + syncDeriver := &SyncDeriver{ + Derivation: derivationPipeline, + Finalizer: finalizer, + SafeHeadNotifs: safeHeadListener, + CLSync: clSync, + Engine: ec, + SyncCfg: syncCfg, + Config: cfg, + L1: l1, + L2: l2, + Emitter: synchronousEvents, + Log: log, + Ctx: driverCtx, + Drain: synchronousEvents.Drain, + } + engDeriv := engine.NewEngDeriver(log, driverCtx, cfg, ec, synchronousEvents) + schedDeriv := NewStepSchedulingDeriver(log, synchronousEvents) + + driver := &Driver{ + l1State: l1State, + SyncDeriver: syncDeriver, + sched: schedDeriv, + synchronousEvents: synchronousEvents, stateReq: make(chan chan struct{}), forceReset: make(chan chan struct{}, 10), startSequencer: make(chan hashAndErrorChannel, 10), stopSequencer: make(chan chan hashAndError, 10), sequencerActive: make(chan chan bool, 10), sequencerNotifs: sequencerStateListener, - config: cfg, - syncCfg: syncCfg, driverConfig: driverCfg, driverCtx: driverCtx, driverCancel: driverCancel, log: log, snapshotLog: snapshotLog, - l1: l1, - l2: l2, sequencer: sequencer, network: network, metrics: metrics, @@ -231,4 +243,18 @@ altSync: altSync, asyncGossiper: asyncGossiper, sequencerConductor: sequencerConductor, } + + *rootDeriver = []rollup.Deriver{ + syncDeriver, + engineResetDeriver, + engDeriv, + schedDeriv, + driver, + clSync, + pipelineDeriver, + attributesHandler, + finalizer, + } + + return driver }
diff --git OP/op-node/rollup/driver/state.go CELO/op-node/rollup/driver/state.go index 7af9656bd3866e6723ba3908de62e4d03c03fa12..73ab3a0923bd03cd1112d6f8bbbd354b65f7af60 100644 --- OP/op-node/rollup/driver/state.go +++ CELO/op-node/rollup/driver/state.go @@ -6,7 +6,6 @@ "context" "encoding/json" "errors" "fmt" - "io" gosync "sync" "time"   @@ -15,12 +14,13 @@ "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/async" + "github.com/ethereum-optimism/optimism/op-node/rollup/clsync" "github.com/ethereum-optimism/optimism/op-node/rollup/conductor" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/engine" + "github.com/ethereum-optimism/optimism/op-node/rollup/finality" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/retry" )   var ( @@ -39,6 +39,10 @@ l1State L1StateIface   *SyncDeriver   + sched *StepSchedulingDeriver + + synchronousEvents *rollup.SynchronousEvents + // Requests to block the event loop for synchronous execution to avoid reading an inconsistent state stateReq chan chan struct{}   @@ -61,18 +65,12 @@ sequencerActive chan chan bool   // sequencerNotifs is notified when the sequencer is started or stopped sequencerNotifs SequencerStateListener - - // Rollup config: rollup chain configuration - config *rollup.Config   sequencerConductor conductor.SequencerConductor   // Driver config: verifier and sequencer settings driverConfig *Config   - // Sync Mod Config - syncCfg *sync.Config - // L1 Signals: // // Not all L1 blocks, or all changes, have to be signalled: @@ -94,8 +92,6 @@ // L2 Signals:   unsafeL2Payloads chan *eth.ExecutionPayloadEnvelope   - l1 L1Chain - l2 L2Chain sequencer SequencerIface network Network // may be nil, network for is optional   @@ -191,39 +187,9 @@ defer s.log.Info("State loop returned")   defer s.driverCancel()   - // stepReqCh is used to request that the driver attempts to step forward by one L1 block. - stepReqCh := make(chan struct{}, 1) - - // channel, nil by default (not firing), but used to schedule re-attempts with delay - var delayedStepReq <-chan time.Time - - // keep track of consecutive failed attempts, to adjust the backoff time accordingly - bOffStrategy := retry.Exponential() - stepAttempts := 0 - - // step requests a derivation step to be taken. Won't deadlock if the channel is full. - step := func() { - select { - case stepReqCh <- struct{}{}: - // Don't deadlock if the channel is already full - default: - } - } - // reqStep requests a derivation step nicely, with a delay if this is a reattempt, or not at all if we already scheduled a reattempt. reqStep := func() { - if stepAttempts > 0 { - // if this is not the first attempt, we re-schedule with a backoff, *without blocking other events* - if delayedStepReq == nil { - delay := bOffStrategy.Duration(stepAttempts) - s.log.Debug("scheduling re-attempt with delay", "attempts", stepAttempts, "delay", delay) - delayedStepReq = time.After(delay) - } else { - s.log.Debug("ignoring step request, already scheduled re-attempt after previous failure", "attempts", stepAttempts) - } - } else { - step() - } + s.Emit(StepReqEvent{}) }   // We call reqStep right away to finish syncing to the tip of the chain if we're behind. @@ -244,7 +210,7 @@ }   // Create a ticker to check if there is a gap in the engine queue. Whenever // there is, we send requests to sync source to retrieve the missing payloads. - syncCheckInterval := time.Duration(s.config.BlockTime) * time.Second * 2 + syncCheckInterval := time.Duration(s.Config.BlockTime) * time.Second * 2 altSyncTicker := time.NewTicker(syncCheckInterval) defer altSyncTicker.Stop() lastUnsafeL2 := s.Engine.UnsafeL2Head() @@ -254,6 +220,15 @@ if s.driverCtx.Err() != nil { // don't try to schedule/handle more work when we are closing. return }   + // While event-processing is synchronous we have to drain + // (i.e. process all queued-up events) before creating any new events. + if err := s.synchronousEvents.Drain(); err != nil { + if s.driverCtx.Err() != nil { + return + } + s.log.Error("unexpected error from event-draining", "err", err) + } + // If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action. // This may adjust at any time based on fork-choice changes or previous errors. // And avoid sequencing if the derivation pipeline indicates the engine is not ready. @@ -294,7 +269,7 @@ // the payload publishing is handled by the async gossiper, which will begin gossiping as soon as available // so, we don't need to receive the payload here _, err := s.sequencer.RunNextSequencerAction(s.driverCtx, s.asyncGossiper, s.sequencerConductor) if errors.Is(err, derive.ErrReset) { - s.Derivation.Reset() + s.Emitter.Emit(rollup.ResetEvent{}) } else if err != nil { s.log.Error("Sequencer critical error", "err", err) return @@ -311,13 +286,13 @@ } case envelope := <-s.unsafeL2Payloads: s.snapshot("New unsafe payload") // If we are doing CL sync or done with engine syncing, fallback to the unsafe payload queue & CL P2P sync. - if s.syncCfg.SyncMode == sync.CLSync || !s.Engine.IsEngineSyncing() { + if s.SyncCfg.SyncMode == sync.CLSync || !s.Engine.IsEngineSyncing() { s.log.Info("Optimistically queueing unsafe L2 execution payload", "id", envelope.ExecutionPayload.ID()) - s.CLSync.AddUnsafePayload(envelope) + s.Emitter.Emit(clsync.ReceivedUnsafePayloadEvent{Envelope: envelope}) s.metrics.RecordReceivedUnsafePayload(envelope) reqStep() - } else if s.syncCfg.SyncMode == sync.ELSync { - ref, err := derive.PayloadToBlockRef(s.config, envelope.ExecutionPayload) + } else if s.SyncCfg.SyncMode == sync.ELSync { + ref, err := derive.PayloadToBlockRef(s.Config, envelope.ExecutionPayload) if err != nil { s.log.Info("Failed to turn execution payload into a block ref", "id", envelope.ExecutionPayload.ID(), "err", err) continue @@ -338,62 +313,12 @@ s.l1State.HandleNewL1SafeBlock(newL1Safe) // no step, justified L1 information does not do anything for L2 derivation or status case newL1Finalized := <-s.l1FinalizedSig: s.l1State.HandleNewL1FinalizedBlock(newL1Finalized) - ctx, cancel := context.WithTimeout(s.driverCtx, time.Second*5) - s.Finalizer.Finalize(ctx, newL1Finalized) - cancel() + s.Emit(finality.FinalizeL1Event{FinalizedL1: newL1Finalized}) reqStep() // we may be able to mark more L2 data as finalized now - case <-delayedStepReq: - delayedStepReq = nil - step() - case <-stepReqCh: - // Don't start the derivation pipeline until we are done with EL sync - if s.Engine.IsEngineSyncing() { - continue - } - s.log.Debug("Sync process step", "onto_origin", s.Derivation.Origin(), "attempts", stepAttempts) - err := s.SyncStep(s.driverCtx) - stepAttempts += 1 // count as attempt by default. We reset to 0 if we are making healthy progress. - if err == io.EOF { - s.log.Debug("Derivation process went idle", "progress", s.Derivation.Origin(), "err", err) - stepAttempts = 0 - continue - } else if err != nil && errors.Is(err, derive.EngineELSyncing) { - s.log.Debug("Derivation process went idle because the engine is syncing", "progress", s.Derivation.Origin(), "unsafe_head", s.Engine.UnsafeL2Head(), "err", err) - stepAttempts = 0 - continue - } else if err != nil && errors.Is(err, derive.ErrReset) { - // If the pipeline corrupts, e.g. due to a reorg, simply reset it - s.log.Warn("Derivation pipeline is reset", "err", err) - s.Derivation.Reset() - s.Finalizer.Reset() - s.metrics.RecordPipelineReset() - reqStep() - if err := engine.ResetEngine(s.driverCtx, s.log, s.config, s.Engine, s.l1, s.l2, s.syncCfg, s.SafeHeadNotifs); err != nil { - s.log.Error("Derivation pipeline not ready, failed to reset engine", "err", err) - // Derivation-pipeline will return a new ResetError until we confirm the engine has been successfully reset. - continue - } - s.Derivation.ConfirmEngineReset() - continue - } else if err != nil && errors.Is(err, derive.ErrTemporary) { - s.log.Warn("Derivation process temporary error", "attempts", stepAttempts, "err", err) - reqStep() - continue - } else if err != nil && errors.Is(err, derive.ErrCritical) { - s.log.Error("Derivation process critical error", "err", err) - return - } else if err != nil && errors.Is(err, derive.NotEnoughData) { - stepAttempts = 0 // don't do a backoff for this error - reqStep() - continue - } else if err != nil { - s.log.Error("Derivation process error", "attempts", stepAttempts, "err", err) - reqStep() - continue - } else { - stepAttempts = 0 - reqStep() // continue with the next step if we can - } + case <-s.sched.NextDelayedStep(): + s.Emit(StepAttemptEvent{}) + case <-s.sched.NextStep(): + s.Emit(StepAttemptEvent{}) case respCh := <-s.stateReq: respCh <- struct{}{} case respCh := <-s.forceReset: @@ -440,6 +365,29 @@ } } }   +// OnEvent handles broadcasted events. +// The Driver itself is a deriver to catch system-critical events. +// Other event-handling should be encapsulated into standalone derivers. +func (s *Driver) OnEvent(ev rollup.Event) { + switch x := ev.(type) { + case rollup.CriticalErrorEvent: + s.Log.Error("Derivation process critical error", "err", x.Err) + // we need to unblock event-processing to be able to close + go func() { + logger := s.Log + err := s.Close() + if err != nil { + logger.Error("Failed to shutdown driver on critical error", "err", err) + } + }() + return + } +} + +func (s *Driver) Emit(ev rollup.Event) { + s.synchronousEvents.Emit(ev) +} + type SyncDeriver struct { // The derivation pipeline is reset whenever we reorg. // The derivation pipeline determines the new l2Safe. @@ -447,74 +395,164 @@ Derivation DerivationPipeline   Finalizer Finalizer   - AttributesHandler AttributesHandler - - SafeHeadNotifs rollup.SafeHeadListener // notified when safe head is updated - lastNotifiedSafeHead eth.L2BlockRef + SafeHeadNotifs rollup.SafeHeadListener // notified when safe head is updated   CLSync CLSync   // The engine controller is used by the sequencer & Derivation components. // We will also use it for EL sync in a future PR. Engine EngineController + + // Sync Mod Config + SyncCfg *sync.Config + + Config *rollup.Config + + L1 L1Chain + L2 L2Chain + + Emitter rollup.EventEmitter + + Log log.Logger + + Ctx context.Context + + Drain func() error }   -// SyncStep performs the sequence of encapsulated syncing steps. -// Warning: this sequence will be broken apart as outlined in op-node derivers design doc. -func (s *SyncDeriver) SyncStep(ctx context.Context) error { - // If we don't need to call FCU to restore unsafeHead using backupUnsafe, keep going b/c - // this was a no-op(except correcting invalid state when backupUnsafe is empty but TryBackupUnsafeReorg called). - if fcuCalled, err := s.Engine.TryBackupUnsafeReorg(ctx); fcuCalled { - // If we needed to perform a network call, then we should yield even if we did not encounter an error. - return err - } - // If we don't need to call FCU, keep going b/c this was a no-op. If we needed to - // perform a network call, then we should yield even if we did not encounter an error. - if err := s.Engine.TryUpdateEngine(ctx); !errors.Is(err, engine.ErrNoFCUNeeded) { - return err - } +func (s *SyncDeriver) OnEvent(ev rollup.Event) { + switch x := ev.(type) { + case StepEvent: + s.onStepEvent() + case rollup.ResetEvent: + s.onResetEvent(x) + case rollup.L1TemporaryErrorEvent: + s.Log.Warn("L1 temporary error", "err", x.Err) + s.Emitter.Emit(StepReqEvent{}) + case rollup.EngineTemporaryErrorEvent: + s.Log.Warn("Engine temporary error", "err", x.Err) + + // Make sure that for any temporarily failed attributes we retry processing. + s.Emitter.Emit(engine.PendingSafeRequestEvent{})   - if s.Engine.IsEngineSyncing() { - // The pipeline cannot move forwards if doing EL sync. - return derive.EngineELSyncing + s.Emitter.Emit(StepReqEvent{}) + case engine.EngineResetConfirmedEvent: + s.onEngineConfirmedReset(x) + case derive.DeriverIdleEvent: + // Once derivation is idle the system is healthy + // and we can wait for new inputs. No backoff necessary. + s.Emitter.Emit(ResetStepBackoffEvent{}) + case derive.DeriverMoreEvent: + // If there is more data to process, + // continue derivation quickly + s.Emitter.Emit(StepReqEvent{ResetBackoff: true}) + case engine.SafeDerivedEvent: + s.onSafeDerivedBlock(x) } +}   - // Trying unsafe payload should be done before safe attributes - // It allows the unsafe head to move forward while the long-range consolidation is in progress. - if err := s.CLSync.Proceed(ctx); err != io.EOF { - // EOF error means we can't process the next unsafe payload. Then we should process next safe attributes. - return err - } - // Try safe attributes now. - if err := s.AttributesHandler.Proceed(ctx); err != io.EOF { - // EOF error means we can't process the next attributes. Then we should derive the next attributes. - return err - } - derivationOrigin := s.Derivation.Origin() - if s.SafeHeadNotifs != nil && s.SafeHeadNotifs.Enabled() && s.Derivation.DerivationReady() && - s.lastNotifiedSafeHead != s.Engine.SafeL2Head() { - s.lastNotifiedSafeHead = s.Engine.SafeL2Head() - // make sure we track the last L2 safe head for every new L1 block - if err := s.SafeHeadNotifs.SafeHeadUpdated(s.lastNotifiedSafeHead, derivationOrigin.ID()); err != nil { +func (s *SyncDeriver) onSafeDerivedBlock(x engine.SafeDerivedEvent) { + if s.SafeHeadNotifs != nil && s.SafeHeadNotifs.Enabled() { + if err := s.SafeHeadNotifs.SafeHeadUpdated(x.Safe, x.DerivedFrom.ID()); err != nil { // At this point our state is in a potentially inconsistent state as we've updated the safe head // in the execution client but failed to post process it. Reset the pipeline so the safe head rolls back // a little (it always rolls back at least 1 block) and then it will retry storing the entry - return derive.NewResetError(fmt.Errorf("safe head notifications failed: %w", err)) + s.Emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("safe head notifications failed: %w", err)}) } } - s.Finalizer.PostProcessSafeL2(s.Engine.SafeL2Head(), derivationOrigin) +}   - // try to finalize the L2 blocks we have synced so far (no-op if L1 finality is behind) - if err := s.Finalizer.OnDerivationL1End(ctx, derivationOrigin); err != nil { - return fmt.Errorf("finalizer OnDerivationL1End error: %w", err) +func (s *SyncDeriver) onEngineConfirmedReset(x engine.EngineResetConfirmedEvent) { + // If the listener update fails, we return, + // and don't confirm the engine-reset with the derivation pipeline. + // The pipeline will re-trigger a reset as necessary. + if s.SafeHeadNotifs != nil { + if err := s.SafeHeadNotifs.SafeHeadReset(x.Safe); err != nil { + s.Log.Error("Failed to warn safe-head notifier of safe-head reset", "safe", x.Safe) + return + } + if s.SafeHeadNotifs.Enabled() && x.Safe.ID() == s.Config.Genesis.L2 { + // The rollup genesis block is always safe by definition. So if the pipeline resets this far back we know + // we will process all safe head updates and can record genesis as always safe from L1 genesis. + // Note that it is not safe to use cfg.Genesis.L1 here as it is the block immediately before the L2 genesis + // but the contracts may have been deployed earlier than that, allowing creating a dispute game + // with a L1 head prior to cfg.Genesis.L1 + l1Genesis, err := s.L1.L1BlockRefByNumber(s.Ctx, 0) + if err != nil { + s.Log.Error("Failed to retrieve L1 genesis, cannot notify genesis as safe block", "err", err) + return + } + if err := s.SafeHeadNotifs.SafeHeadUpdated(x.Safe, l1Genesis.ID()); err != nil { + s.Log.Error("Failed to notify safe-head listener of safe-head", "err", err) + return + } + } } + s.Emitter.Emit(derive.ConfirmPipelineResetEvent{}) +}   - attr, err := s.Derivation.Step(ctx, s.Engine.PendingSafeL2Head()) - if err != nil { +func (s *SyncDeriver) onStepEvent() { + s.Log.Debug("Sync process step") + // Note: while we refactor the SyncStep to be entirely event-based we have an intermediate phase + // where some things are triggered through events, and some through this synchronous step function. + // We just translate the results into their equivalent events, + // to merge the error-handling with that of the new event-based system. + err := s.SyncStep() + if err != nil && errors.Is(err, derive.EngineELSyncing) { + s.Log.Debug("Derivation process went idle because the engine is syncing", "unsafe_head", s.Engine.UnsafeL2Head(), "err", err) + s.Emitter.Emit(ResetStepBackoffEvent{}) + } else if err != nil && errors.Is(err, derive.ErrReset) { + s.Emitter.Emit(rollup.ResetEvent{Err: err}) + } else if err != nil && errors.Is(err, derive.ErrTemporary) { + s.Emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: err}) + } else if err != nil && errors.Is(err, derive.ErrCritical) { + s.Emitter.Emit(rollup.CriticalErrorEvent{Err: err}) + } else if err != nil { + s.Log.Error("Derivation process error", "err", err) + s.Emitter.Emit(StepReqEvent{}) + } else { + s.Emitter.Emit(StepReqEvent{ResetBackoff: true}) // continue with the next step if we can + } +} + +func (s *SyncDeriver) onResetEvent(x rollup.ResetEvent) { + // If the system corrupts, e.g. due to a reorg, simply reset it + s.Log.Warn("Deriver system is resetting", "err", x.Err) + s.Emitter.Emit(StepReqEvent{}) + s.Emitter.Emit(engine.ResetEngineRequestEvent{}) +} + +// SyncStep performs the sequence of encapsulated syncing steps. +// Warning: this sequence will be broken apart as outlined in op-node derivers design doc. +func (s *SyncDeriver) SyncStep() error { + if err := s.Drain(); err != nil { + return err + } + + s.Emitter.Emit(engine.TryBackupUnsafeReorgEvent{}) + if err := s.Drain(); err != nil { return err }   - s.AttributesHandler.SetAttributes(attr) + s.Emitter.Emit(engine.TryUpdateEngineEvent{}) + if err := s.Drain(); err != nil { + return err + } + + if s.Engine.IsEngineSyncing() { + // The pipeline cannot move forwards if doing EL sync. + return derive.EngineELSyncing + } + + // Any now processed forkchoice updates will trigger CL-sync payload processing, if any payload is queued up. + + // Since we don't force attributes to be processed at this point, + // we cannot safely directly trigger the derivation, as that may generate new attributes that + // conflict with what attributes have not been applied yet. + // Instead, we request the engine to repeat where its pending-safe head is at. + // Upon the pending-safe signal the attributes deriver can then ask the pipeline + // to generate new attributes, if no attributes are known already. + s.Emitter.Emit(engine.PendingSafeRequestEvent{}) return nil }   @@ -598,6 +636,10 @@ } } }   +func (s *Driver) OverrideLeader(ctx context.Context) error { + return s.sequencerConductor.OverrideLeader(ctx) +} + // syncStatus returns the current sync status, and should only be called synchronously with // the driver event loop to avoid retrieval of an inconsistent status. func (s *Driver) syncStatus() *eth.SyncStatus { @@ -636,7 +678,7 @@ wait := make(chan struct{}) select { case s.stateReq <- wait: resp := s.syncStatus() - ref, err := s.l2.L2BlockRefByNumber(ctx, num) + ref, err := s.L2.L2BlockRefByNumber(ctx, num) <-wait return ref, resp, err case <-ctx.Done(): @@ -658,7 +700,6 @@ func (s *Driver) snapshot(event string) { s.snapshotLog.Info("Rollup State Snapshot", "event", event, "l1Head", deferJSONString{s.l1State.L1Head()}, - "l1Current", deferJSONString{s.Derivation.Origin()}, "l2Head", deferJSONString{s.Engine.UnsafeL2Head()}, "l2Safe", deferJSONString{s.Engine.SafeL2Head()}, "l2FinalizedHead", deferJSONString{s.Engine.Finalized()})
diff --git OP/op-node/rollup/driver/steps.go CELO/op-node/rollup/driver/steps.go new file mode 100644 index 0000000000000000000000000000000000000000..8f29203adcc34894c0e31bbaf0d439952fa6675d --- /dev/null +++ CELO/op-node/rollup/driver/steps.go @@ -0,0 +1,130 @@ +package driver + +import ( + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/retry" +) + +type ResetStepBackoffEvent struct { +} + +func (ev ResetStepBackoffEvent) String() string { + return "reset-step-backoff" +} + +type StepReqEvent struct { + ResetBackoff bool +} + +func (ev StepReqEvent) String() string { + return "step-req" +} + +type StepAttemptEvent struct{} + +func (ev StepAttemptEvent) String() string { + return "step-attempt" +} + +type StepEvent struct{} + +func (ev StepEvent) String() string { + return "step" +} + +// StepSchedulingDeriver is a deriver that emits StepEvent events. +// The deriver can be requested to schedule a step with a StepReqEvent. +// +// It is then up to the caller to translate scheduling into StepAttemptEvent emissions, by waiting for +// NextStep or NextDelayedStep channels (nil if there is nothing to wait for, for channel-merging purposes). +// +// Upon StepAttemptEvent the scheduler will then emit a StepEvent, +// while maintaining backoff state, to not spam steps. +// +// Backoff can be reset by sending a request with StepReqEvent.ResetBackoff +// set to true, or by sending a ResetStepBackoffEvent. +type StepSchedulingDeriver struct { + + // keep track of consecutive failed attempts, to adjust the backoff time accordingly + stepAttempts int + bOffStrategy retry.Strategy + + // channel, nil by default (not firing), but used to schedule re-attempts with delay + delayedStepReq <-chan time.Time + + // stepReqCh is used to request that the driver attempts to step forward by one L1 block. + stepReqCh chan struct{} + + log log.Logger + + emitter rollup.EventEmitter +} + +func NewStepSchedulingDeriver(log log.Logger, emitter rollup.EventEmitter) *StepSchedulingDeriver { + return &StepSchedulingDeriver{ + stepAttempts: 0, + bOffStrategy: retry.Exponential(), + stepReqCh: make(chan struct{}, 1), + delayedStepReq: nil, + log: log, + emitter: emitter, + } +} + +// NextStep is a channel to await, and if triggered, +// the caller should emit a StepAttemptEvent to queue up a step while maintaining backoff. +func (s *StepSchedulingDeriver) NextStep() <-chan struct{} { + return s.stepReqCh +} + +// NextDelayedStep is a temporary channel to await, and if triggered, +// the caller should emit a StepAttemptEvent to queue up a step while maintaining backoff. +// The returned channel may be nil, if there is no requested step with delay scheduled. +func (s *StepSchedulingDeriver) NextDelayedStep() <-chan time.Time { + return s.delayedStepReq +} + +func (s *StepSchedulingDeriver) OnEvent(ev rollup.Event) { + step := func() { + s.delayedStepReq = nil + select { + case s.stepReqCh <- struct{}{}: + // Don't deadlock if the channel is already full + default: + } + } + + switch x := ev.(type) { + case StepReqEvent: + if x.ResetBackoff { + s.stepAttempts = 0 + } + if s.stepAttempts > 0 { + // if this is not the first attempt, we re-schedule with a backoff, *without blocking other events* + if s.delayedStepReq == nil { + delay := s.bOffStrategy.Duration(s.stepAttempts) + s.log.Debug("scheduling re-attempt with delay", "attempts", s.stepAttempts, "delay", delay) + s.delayedStepReq = time.After(delay) + } else { + s.log.Debug("ignoring step request, already scheduled re-attempt after previous failure", "attempts", s.stepAttempts) + } + } else { + step() + } + case StepAttemptEvent: + // clear the delayed-step channel + s.delayedStepReq = nil + if s.stepAttempts > 0 { + s.log.Debug("Running step retry", "attempts", s.stepAttempts) + } + // count as attempt by default. We reset to 0 if we are making healthy progress. + s.stepAttempts += 1 + s.emitter.Emit(StepEvent{}) + case ResetStepBackoffEvent: + s.stepAttempts = 0 + } +}
diff --git OP/op-node/rollup/driver/steps_test.go CELO/op-node/rollup/driver/steps_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5dbba314b6e1cf4170946817a1feff5c16d4dae6 --- /dev/null +++ CELO/op-node/rollup/driver/steps_test.go @@ -0,0 +1,53 @@ +package driver + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +func TestStepSchedulingDeriver(t *testing.T) { + logger := testlog.Logger(t, log.LevelError) + var queued []rollup.Event + emitter := rollup.EmitterFunc(func(ev rollup.Event) { + queued = append(queued, ev) + }) + sched := NewStepSchedulingDeriver(logger, emitter) + require.Len(t, sched.NextStep(), 0, "start empty") + sched.OnEvent(StepReqEvent{}) + require.Len(t, sched.NextStep(), 1, "take request") + sched.OnEvent(StepReqEvent{}) + require.Len(t, sched.NextStep(), 1, "ignore duplicate request") + require.Empty(t, queued, "only scheduled so far, no step attempts yet") + <-sched.NextStep() + sched.OnEvent(StepAttemptEvent{}) + require.Equal(t, []rollup.Event{StepEvent{}}, queued, "got step event") + require.Nil(t, sched.NextDelayedStep(), "no delayed steps yet") + sched.OnEvent(StepReqEvent{}) + require.NotNil(t, sched.NextDelayedStep(), "2nd attempt before backoff reset causes delayed step to be scheduled") + sched.OnEvent(StepReqEvent{}) + require.NotNil(t, sched.NextDelayedStep(), "can continue to request attempts") + + sched.OnEvent(StepReqEvent{}) + require.Len(t, sched.NextStep(), 0, "no step requests accepted without delay if backoff is counting") + + sched.OnEvent(StepReqEvent{ResetBackoff: true}) + require.Len(t, sched.NextStep(), 1, "request accepted if backoff is reset") + <-sched.NextStep() + + sched.OnEvent(StepReqEvent{}) + require.Len(t, sched.NextStep(), 1, "no backoff, no attempt has been made yet") + <-sched.NextStep() + sched.OnEvent(StepAttemptEvent{}) + sched.OnEvent(StepReqEvent{}) + require.Len(t, sched.NextStep(), 0, "backoff again") + + sched.OnEvent(ResetStepBackoffEvent{}) + sched.OnEvent(StepReqEvent{}) + require.Len(t, sched.NextStep(), 1, "reset backoff accepted, was able to schedule non-delayed step") +}
diff --git OP/op-node/rollup/engine/engine_controller.go CELO/op-node/rollup/engine/engine_controller.go index 6e382cc367f2a3f186816d9a86270f2c36cba780..8121dc3800aa61aab6a0edf593e5df89cf28fdb7 100644 --- OP/op-node/rollup/engine/engine_controller.go +++ CELO/op-node/rollup/engine/engine_controller.go @@ -54,6 +54,8 @@ rollupCfg *rollup.Config elStart time.Time clock clock.Clock   + emitter rollup.EventEmitter + // Block Head State unsafeHead eth.L2BlockRef pendingSafeHead eth.L2BlockRef // L2 block processed from the middle of a span batch, but not marked as the safe block yet. @@ -75,7 +77,8 @@ buildingSafe bool safeAttrs *derive.AttributesWithParent }   -func NewEngineController(engine ExecEngine, log log.Logger, metrics derive.Metrics, rollupCfg *rollup.Config, syncMode sync.Mode) *EngineController { +func NewEngineController(engine ExecEngine, log log.Logger, metrics derive.Metrics, + rollupCfg *rollup.Config, syncMode sync.Mode, emitter rollup.EventEmitter) *EngineController { syncStatus := syncStatusCL if syncMode == sync.ELSync { syncStatus = syncStatusWillStartEL @@ -90,6 +93,7 @@ rollupCfg: rollupCfg, syncMode: syncMode, syncStatus: syncStatus, clock: clock.SystemClock, + emitter: emitter, } }   @@ -224,6 +228,11 @@ id, errTyp, err := startPayload(ctx, e.engine, fc, attrs.Attributes) if err != nil { return errTyp, err } + e.emitter.Emit(ForkchoiceUpdateEvent{ + UnsafeL2Head: parent, + SafeL2Head: e.safeHead, + FinalizedL2Head: e.finalizedHead, + })   e.buildingInfo = eth.PayloadInfo{ID: id, Timestamp: uint64(attrs.Attributes.Timestamp)} e.buildingSafe = updateSafe @@ -280,6 +289,11 @@ // Remove backupUnsafeHead because this backup will be never used after consolidation. e.SetBackupUnsafeL2Head(eth.L2BlockRef{}, false) } } + e.emitter.Emit(ForkchoiceUpdateEvent{ + UnsafeL2Head: e.unsafeHead, + SafeL2Head: e.safeHead, + FinalizedL2Head: e.finalizedHead, + })   e.resetBuildingState() return envelope, BlockInsertOK, nil @@ -353,7 +367,7 @@ FinalizedBlockHash: e.finalizedHead.Hash, } logFn := e.logSyncProgressMaybe() defer logFn() - _, err := e.engine.ForkchoiceUpdate(ctx, &fc, nil) + fcRes, err := e.engine.ForkchoiceUpdate(ctx, &fc, nil) if err != nil { var inputErr eth.InputError if errors.As(err, &inputErr) { @@ -367,6 +381,13 @@ } else { return derive.NewTemporaryError(fmt.Errorf("failed to sync forkchoice with engine: %w", err)) } } + if fcRes.PayloadStatus.Status == eth.ExecutionValid { + e.emitter.Emit(ForkchoiceUpdateEvent{ + UnsafeL2Head: e.unsafeHead, + SafeL2Head: e.safeHead, + FinalizedL2Head: e.finalizedHead, + }) + } e.needFCUCall = false return nil } @@ -392,6 +413,9 @@ // Insert the payload & then call FCU status, err := e.engine.NewPayload(ctx, envelope.ExecutionPayload, envelope.ParentBeaconBlockRoot) if err != nil { return derive.NewTemporaryError(fmt.Errorf("failed to update insert payload: %w", err)) + } + if status.Status == eth.ExecutionInvalid { + e.emitter.Emit(InvalidPayloadEvent{Envelope: envelope}) } if !e.checkNewPayloadStatus(status.Status) { payload := envelope.ExecutionPayload @@ -440,6 +464,14 @@ e.log.Info("Finished EL sync", "sync_duration", e.clock.Since(e.elStart), "finalized_block", ref.ID().String()) e.syncStatus = syncStatusFinishedEL }   + if fcRes.PayloadStatus.Status == eth.ExecutionValid { + e.emitter.Emit(ForkchoiceUpdateEvent{ + UnsafeL2Head: e.unsafeHead, + SafeL2Head: e.safeHead, + FinalizedL2Head: e.finalizedHead, + }) + } + return nil }   @@ -501,6 +533,11 @@ return true, derive.NewTemporaryError(fmt.Errorf("failed to sync forkchoice with engine: %w", err)) } } if fcRes.PayloadStatus.Status == eth.ExecutionValid { + e.emitter.Emit(ForkchoiceUpdateEvent{ + UnsafeL2Head: e.backupUnsafeHead, + SafeL2Head: e.safeHead, + FinalizedL2Head: e.finalizedHead, + }) // Execution engine accepted the reorg. e.log.Info("successfully reorged unsafe head using backupUnsafe", "unsafe", e.backupUnsafeHead.ID()) e.SetUnsafeHead(e.BackupUnsafeL2Head())
diff --git OP/op-node/rollup/engine/engine_reset.go CELO/op-node/rollup/engine/engine_reset.go index 700e44e11c30185318f6f346121d2c8645a00eb2..c0985d8ddb39347fd6e0c23cf95087b8b2a15de2 100644 --- OP/op-node/rollup/engine/engine_reset.go +++ CELO/op-node/rollup/engine/engine_reset.go @@ -7,72 +7,54 @@ "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" - "github.com/ethereum-optimism/optimism/op-service/eth" )   -type ResetL2 interface { - sync.L2Chain - derive.SystemConfigL2Fetcher +// ResetEngineRequestEvent requests the EngineResetDeriver to walk +// the L2 chain backwards until it finds a plausible unsafe head, +// and find an L2 safe block that is guaranteed to still be from the L1 chain. +type ResetEngineRequestEvent struct{} + +func (ev ResetEngineRequestEvent) String() string { + return "reset-engine-request" }   -type ResetEngineControl interface { - SetUnsafeHead(eth.L2BlockRef) - SetSafeHead(eth.L2BlockRef) - SetFinalizedHead(eth.L2BlockRef) +type EngineResetDeriver struct { + ctx context.Context + log log.Logger + cfg *rollup.Config + l1 sync.L1Chain + l2 sync.L2Chain + syncCfg *sync.Config   - SetBackupUnsafeL2Head(block eth.L2BlockRef, triggerReorg bool) - SetPendingSafeL2Head(eth.L2BlockRef) - - ResetBuildingState() + emitter rollup.EventEmitter }   -// ResetEngine walks the L2 chain backwards until it finds a plausible unsafe head, -// and an L2 safe block that is guaranteed to still be from the L1 chain. -func ResetEngine(ctx context.Context, log log.Logger, cfg *rollup.Config, ec ResetEngineControl, l1 sync.L1Chain, l2 ResetL2, syncCfg *sync.Config, safeHeadNotifs rollup.SafeHeadListener) error { - result, err := sync.FindL2Heads(ctx, cfg, l1, l2, log, syncCfg) - if err != nil { - return derive.NewTemporaryError(fmt.Errorf("failed to find the L2 Heads to start from: %w", err)) - } - finalized, safe, unsafe := result.Finalized, result.Safe, result.Unsafe - l1Origin, err := l1.L1BlockRefByHash(ctx, safe.L1Origin.Hash) - if err != nil { - return derive.NewTemporaryError(fmt.Errorf("failed to fetch the new L1 progress: origin: %v; err: %w", safe.L1Origin, err)) - } - if safe.Time < l1Origin.Time { - return derive.NewResetError(fmt.Errorf("cannot reset block derivation to start at L2 block %s with time %d older than its L1 origin %s with time %d, time invariant is broken", - safe, safe.Time, l1Origin, l1Origin.Time)) +func NewEngineResetDeriver(ctx context.Context, log log.Logger, cfg *rollup.Config, + l1 sync.L1Chain, l2 sync.L2Chain, syncCfg *sync.Config, emitter rollup.EventEmitter) *EngineResetDeriver { + return &EngineResetDeriver{ + ctx: ctx, + log: log, + cfg: cfg, + l1: l1, + l2: l2, + syncCfg: syncCfg, + emitter: emitter, } - - ec.SetUnsafeHead(unsafe) - ec.SetSafeHead(safe) - ec.SetPendingSafeL2Head(safe) - ec.SetFinalizedHead(finalized) - ec.SetBackupUnsafeL2Head(eth.L2BlockRef{}, false) - ec.ResetBuildingState() - - log.Debug("Reset of Engine is completed", "safeHead", safe, "unsafe", unsafe, "safe_timestamp", safe.Time, - "unsafe_timestamp", unsafe.Time, "l1Origin", l1Origin) +}   - if safeHeadNotifs != nil { - if err := safeHeadNotifs.SafeHeadReset(safe); err != nil { - return err +func (d *EngineResetDeriver) OnEvent(ev rollup.Event) { + switch ev.(type) { + case ResetEngineRequestEvent: + result, err := sync.FindL2Heads(d.ctx, d.cfg, d.l1, d.l2, d.log, d.syncCfg) + if err != nil { + d.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("failed to find the L2 Heads to start from: %w", err)}) + return } - if safeHeadNotifs.Enabled() && safe.Number == cfg.Genesis.L2.Number && safe.Hash == cfg.Genesis.L2.Hash { - // The rollup genesis block is always safe by definition. So if the pipeline resets this far back we know - // we will process all safe head updates and can record genesis as always safe from L1 genesis. - // Note that it is not safe to use cfg.Genesis.L1 here as it is the block immediately before the L2 genesis - // but the contracts may have been deployed earlier than that, allowing creating a dispute game - // with a L1 head prior to cfg.Genesis.L1 - l1Genesis, err := l1.L1BlockRefByNumber(ctx, 0) - if err != nil { - return fmt.Errorf("failed to retrieve L1 genesis: %w", err) - } - if err := safeHeadNotifs.SafeHeadUpdated(safe, l1Genesis.ID()); err != nil { - return err - } - } + d.emitter.Emit(ForceEngineResetEvent{ + Unsafe: result.Unsafe, + Safe: result.Safe, + Finalized: result.Finalized, + }) } - return nil }
diff --git OP/op-node/rollup/engine/events.go CELO/op-node/rollup/engine/events.go new file mode 100644 index 0000000000000000000000000000000000000000..4c2320310a86a3fe44aeb0e8ee87cb8289ba7511 --- /dev/null +++ CELO/op-node/rollup/engine/events.go @@ -0,0 +1,360 @@ +package engine + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/async" + "github.com/ethereum-optimism/optimism/op-node/rollup/conductor" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type InvalidPayloadEvent struct { + Envelope *eth.ExecutionPayloadEnvelope +} + +func (ev InvalidPayloadEvent) String() string { + return "invalid-payload" +} + +type InvalidPayloadAttributesEvent struct { + Attributes *derive.AttributesWithParent +} + +func (ev InvalidPayloadAttributesEvent) String() string { + return "invalid-payload-attributes" +} + +// ForkchoiceRequestEvent signals to the engine that it should emit an artificial +// forkchoice-update event, to signal the latest forkchoice to other derivers. +// This helps decouple derivers from the actual engine state, +// while also not making the derivers wait for a forkchoice update at random. +type ForkchoiceRequestEvent struct { +} + +func (ev ForkchoiceRequestEvent) String() string { + return "forkchoice-request" +} + +type ForkchoiceUpdateEvent struct { + UnsafeL2Head, SafeL2Head, FinalizedL2Head eth.L2BlockRef +} + +func (ev ForkchoiceUpdateEvent) String() string { + return "forkchoice-update" +} + +type PendingSafeUpdateEvent struct { + PendingSafe eth.L2BlockRef + Unsafe eth.L2BlockRef // tip, added to the signal, to determine if there are existing blocks to consolidate +} + +func (ev PendingSafeUpdateEvent) String() string { + return "pending-safe-update" +} + +// PromotePendingSafeEvent signals that a block can be marked as pending-safe, and/or safe. +type PromotePendingSafeEvent struct { + Ref eth.L2BlockRef + Safe bool + DerivedFrom eth.L1BlockRef +} + +func (ev PromotePendingSafeEvent) String() string { + return "promote-pending-safe" +} + +// SafeDerivedEvent signals that a block was determined to be safe, and derived from the given L1 block +type SafeDerivedEvent struct { + Safe eth.L2BlockRef + DerivedFrom eth.L1BlockRef +} + +func (ev SafeDerivedEvent) String() string { + return "safe-derived" +} + +type ProcessAttributesEvent struct { + Attributes *derive.AttributesWithParent +} + +func (ev ProcessAttributesEvent) String() string { + return "process-attributes" +} + +type PendingSafeRequestEvent struct { +} + +func (ev PendingSafeRequestEvent) String() string { + return "pending-safe-request" +} + +type ProcessUnsafePayloadEvent struct { + Envelope *eth.ExecutionPayloadEnvelope +} + +func (ev ProcessUnsafePayloadEvent) String() string { + return "process-unsafe-payload" +} + +type TryBackupUnsafeReorgEvent struct { +} + +func (ev TryBackupUnsafeReorgEvent) String() string { + return "try-backup-unsafe-reorg" +} + +type TryUpdateEngineEvent struct { +} + +func (ev TryUpdateEngineEvent) String() string { + return "try-update-engine" +} + +type ForceEngineResetEvent struct { + Unsafe, Safe, Finalized eth.L2BlockRef +} + +func (ev ForceEngineResetEvent) String() string { + return "force-engine-reset" +} + +type EngineResetConfirmedEvent struct { + Unsafe, Safe, Finalized eth.L2BlockRef +} + +func (ev EngineResetConfirmedEvent) String() string { + return "engine-reset-confirmed" +} + +// PromoteFinalizedEvent signals that a block can be marked as finalized. +type PromoteFinalizedEvent struct { + Ref eth.L2BlockRef +} + +func (ev PromoteFinalizedEvent) String() string { + return "promote-finalized" +} + +type EngDeriver struct { + log log.Logger + cfg *rollup.Config + ec *EngineController + ctx context.Context + emitter rollup.EventEmitter +} + +var _ rollup.Deriver = (*EngDeriver)(nil) + +func NewEngDeriver(log log.Logger, ctx context.Context, cfg *rollup.Config, + ec *EngineController, emitter rollup.EventEmitter) *EngDeriver { + return &EngDeriver{ + log: log, + cfg: cfg, + ec: ec, + ctx: ctx, + emitter: emitter, + } +} + +func (d *EngDeriver) OnEvent(ev rollup.Event) { + switch x := ev.(type) { + case TryBackupUnsafeReorgEvent: + // If we don't need to call FCU to restore unsafeHead using backupUnsafe, keep going b/c + // this was a no-op(except correcting invalid state when backupUnsafe is empty but TryBackupUnsafeReorg called). + fcuCalled, err := d.ec.TryBackupUnsafeReorg(d.ctx) + // Dealing with legacy here: it used to skip over the error-handling if fcuCalled was false. + // But that combination is not actually a code-path in TryBackupUnsafeReorg. + // We should drop fcuCalled, and make the function emit events directly, + // once there are no more synchronous callers. + if !fcuCalled && err != nil { + d.log.Crit("unexpected TryBackupUnsafeReorg error after no FCU call", "err", err) + } + if err != nil { + // If we needed to perform a network call, then we should yield even if we did not encounter an error. + if errors.Is(err, derive.ErrReset) { + d.emitter.Emit(rollup.ResetEvent{Err: err}) + } else if errors.Is(err, derive.ErrTemporary) { + d.emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: err}) + } else { + d.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("unexpected TryBackupUnsafeReorg error type: %w", err)}) + } + } + case TryUpdateEngineEvent: + // If we don't need to call FCU, keep going b/c this was a no-op. If we needed to + // perform a network call, then we should yield even if we did not encounter an error. + if err := d.ec.TryUpdateEngine(d.ctx); err != nil && !errors.Is(err, ErrNoFCUNeeded) { + if errors.Is(err, derive.ErrReset) { + d.emitter.Emit(rollup.ResetEvent{Err: err}) + } else if errors.Is(err, derive.ErrTemporary) { + d.emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: err}) + } else { + d.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("unexpected TryUpdateEngine error type: %w", err)}) + } + } + case ProcessUnsafePayloadEvent: + ref, err := derive.PayloadToBlockRef(d.cfg, x.Envelope.ExecutionPayload) + if err != nil { + d.log.Error("failed to decode L2 block ref from payload", "err", err) + return + } + if err := d.ec.InsertUnsafePayload(d.ctx, x.Envelope, ref); err != nil { + d.log.Info("failed to insert payload", "ref", ref, + "txs", len(x.Envelope.ExecutionPayload.Transactions), "err", err) + // yes, duplicate error-handling. After all derivers are interacting with the engine + // through events, we can drop the engine-controller interface: + // unify the events handler with the engine-controller, + // remove a lot of code, and not do this error translation. + if errors.Is(err, derive.ErrReset) { + d.emitter.Emit(rollup.ResetEvent{Err: err}) + } else if errors.Is(err, derive.ErrTemporary) { + d.emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: err}) + } else { + d.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("unexpected InsertUnsafePayload error type: %w", err)}) + } + } else { + d.log.Info("successfully processed payload", "ref", ref, "txs", len(x.Envelope.ExecutionPayload.Transactions)) + } + case ForkchoiceRequestEvent: + d.emitter.Emit(ForkchoiceUpdateEvent{ + UnsafeL2Head: d.ec.UnsafeL2Head(), + SafeL2Head: d.ec.SafeL2Head(), + FinalizedL2Head: d.ec.Finalized(), + }) + case ForceEngineResetEvent: + ForceEngineReset(d.ec, x) + + // Time to apply the changes to the underlying engine + d.emitter.Emit(TryUpdateEngineEvent{}) + + log.Debug("Reset of Engine is completed", + "safeHead", x.Safe, "unsafe", x.Unsafe, "safe_timestamp", x.Safe.Time, + "unsafe_timestamp", x.Unsafe.Time) + d.emitter.Emit(EngineResetConfirmedEvent(x)) + case ProcessAttributesEvent: + d.onForceNextSafeAttributes(x.Attributes) + case PendingSafeRequestEvent: + d.emitter.Emit(PendingSafeUpdateEvent{ + PendingSafe: d.ec.PendingSafeL2Head(), + Unsafe: d.ec.UnsafeL2Head(), + }) + case PromotePendingSafeEvent: + // Only promote if not already stale. + // Resets/overwrites happen through engine-resets, not through promotion. + if x.Ref.Number > d.ec.PendingSafeL2Head().Number { + d.ec.SetPendingSafeL2Head(x.Ref) + } + if x.Safe && x.Ref.Number > d.ec.SafeL2Head().Number { + d.ec.SetSafeHead(x.Ref) + d.emitter.Emit(SafeDerivedEvent{Safe: x.Ref, DerivedFrom: x.DerivedFrom}) + } + case PromoteFinalizedEvent: + if x.Ref.Number < d.ec.Finalized().Number { + d.log.Error("Cannot rewind finality,", "ref", x.Ref, "finalized", d.ec.Finalized()) + return + } + if x.Ref.Number > d.ec.SafeL2Head().Number { + d.log.Error("Block must be safe before it can be finalized", "ref", x.Ref, "safe", d.ec.SafeL2Head()) + return + } + d.ec.SetFinalizedHead(x.Ref) + } +} + +// onForceNextSafeAttributes inserts the provided attributes, reorging away any conflicting unsafe chain. +func (eq *EngDeriver) onForceNextSafeAttributes(attributes *derive.AttributesWithParent) { + ctx, cancel := context.WithTimeout(eq.ctx, time.Second*10) + defer cancel() + + attrs := attributes.Attributes + errType, err := eq.ec.StartPayload(ctx, eq.ec.PendingSafeL2Head(), attributes, true) + var envelope *eth.ExecutionPayloadEnvelope + if err == nil { + envelope, errType, err = eq.ec.ConfirmPayload(ctx, async.NoOpGossiper{}, &conductor.NoOpConductor{}) + } + if err != nil { + switch errType { + case BlockInsertTemporaryErr: + // RPC errors are recoverable, we can retry the buffered payload attributes later. + eq.emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: fmt.Errorf("temporarily cannot insert new safe block: %w", err)}) + return + case BlockInsertPrestateErr: + _ = eq.ec.CancelPayload(ctx, true) + eq.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("need reset to resolve pre-state problem: %w", err)}) + return + case BlockInsertPayloadErr: + if !errors.Is(err, derive.ErrTemporary) { + eq.emitter.Emit(InvalidPayloadAttributesEvent{Attributes: attributes}) + } + _ = eq.ec.CancelPayload(ctx, true) + eq.log.Warn("could not process payload derived from L1 data, dropping attributes", "err", err) + // Count the number of deposits to see if the tx list is deposit only. + depositCount := 0 + for _, tx := range attrs.Transactions { + if len(tx) > 0 && tx[0] == types.DepositTxType { + depositCount += 1 + } + } + // Deposit transaction execution errors are suppressed in the execution engine, but if the + // block is somehow invalid, there is nothing we can do to recover & we should exit. + if len(attrs.Transactions) == depositCount { + eq.log.Error("deposit only block was invalid", "parent", attributes.Parent, "err", err) + eq.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("failed to process block with only deposit transactions: %w", err)}) + return + } + // Revert the pending safe head to the safe head. + eq.ec.SetPendingSafeL2Head(eq.ec.SafeL2Head()) + // suppress the error b/c we want to retry with the next batch from the batch queue + // If there is no valid batch the node will eventually force a deposit only block. If + // the deposit only block fails, this will return the critical error above. + + // Try to restore to previous known unsafe chain. + eq.ec.SetBackupUnsafeL2Head(eq.ec.BackupUnsafeL2Head(), true) + + // drop the payload without inserting it into the engine + return + default: + eq.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("unknown InsertHeadBlock error type %d: %w", errType, err)}) + } + } + ref, err := derive.PayloadToBlockRef(eq.cfg, envelope.ExecutionPayload) + if err != nil { + eq.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("failed to decode L2 block ref from payload: %w", err)}) + return + } + eq.ec.SetPendingSafeL2Head(ref) + if attributes.IsLastInSpan { + eq.ec.SetSafeHead(ref) + eq.emitter.Emit(SafeDerivedEvent{Safe: ref, DerivedFrom: attributes.DerivedFrom}) + } + eq.emitter.Emit(PendingSafeUpdateEvent{ + PendingSafe: eq.ec.PendingSafeL2Head(), + Unsafe: eq.ec.UnsafeL2Head(), + }) +} + +type ResetEngineControl interface { + SetUnsafeHead(eth.L2BlockRef) + SetSafeHead(eth.L2BlockRef) + SetFinalizedHead(eth.L2BlockRef) + SetBackupUnsafeL2Head(block eth.L2BlockRef, triggerReorg bool) + SetPendingSafeL2Head(eth.L2BlockRef) + ResetBuildingState() +} + +// ForceEngineReset is not to be used. The op-program needs it for now, until event processing is adopted there. +func ForceEngineReset(ec ResetEngineControl, x ForceEngineResetEvent) { + ec.SetUnsafeHead(x.Unsafe) + ec.SetSafeHead(x.Safe) + ec.SetPendingSafeL2Head(x.Safe) + ec.SetFinalizedHead(x.Finalized) + ec.SetBackupUnsafeL2Head(eth.L2BlockRef{}, false) + ec.ResetBuildingState() +}
diff --git OP/op-node/rollup/events.go CELO/op-node/rollup/events.go new file mode 100644 index 0000000000000000000000000000000000000000..29a6acdd14172912d95aa68658529921eac97a41 --- /dev/null +++ CELO/op-node/rollup/events.go @@ -0,0 +1,101 @@ +package rollup + +import "github.com/ethereum/go-ethereum/log" + +type Event interface { + String() string +} + +type Deriver interface { + OnEvent(ev Event) +} + +type EventEmitter interface { + Emit(ev Event) +} + +type EmitterFunc func(ev Event) + +func (fn EmitterFunc) Emit(ev Event) { + fn(ev) +} + +// L1TemporaryErrorEvent identifies a temporary issue with the L1 data. +type L1TemporaryErrorEvent struct { + Err error +} + +var _ Event = L1TemporaryErrorEvent{} + +func (ev L1TemporaryErrorEvent) String() string { + return "l1-temporary-error" +} + +// EngineTemporaryErrorEvent identifies a temporary processing issue. +// It applies to both L1 and L2 data, often inter-related. +// This scope will be reduced over time, to only capture L2-engine specific temporary errors. +// See L1TemporaryErrorEvent for L1 related temporary errors. +type EngineTemporaryErrorEvent struct { + Err error +} + +var _ Event = EngineTemporaryErrorEvent{} + +func (ev EngineTemporaryErrorEvent) String() string { + return "engine-temporary-error" +} + +type ResetEvent struct { + Err error +} + +var _ Event = ResetEvent{} + +func (ev ResetEvent) String() string { + return "reset-event" +} + +type CriticalErrorEvent struct { + Err error +} + +var _ Event = CriticalErrorEvent{} + +func (ev CriticalErrorEvent) String() string { + return "critical-error" +} + +type SynchronousDerivers []Deriver + +func (s *SynchronousDerivers) OnEvent(ev Event) { + for _, d := range *s { + d.OnEvent(ev) + } +} + +var _ Deriver = (*SynchronousDerivers)(nil) + +type DebugDeriver struct { + Log log.Logger +} + +func (d DebugDeriver) OnEvent(ev Event) { + d.Log.Debug("on-event", "event", ev) +} + +type NoopDeriver struct{} + +func (d NoopDeriver) OnEvent(ev Event) {} + +// DeriverFunc implements the Deriver interface as a function, +// similar to how the std-lib http HandlerFunc implements a Handler. +// This can be used for small in-place derivers, test helpers, etc. +type DeriverFunc func(ev Event) + +func (fn DeriverFunc) OnEvent(ev Event) { + fn(ev) +} + +type NoopEmitter struct{} + +func (e NoopEmitter) Emit(ev Event) {}
diff --git OP/op-node/rollup/events_test.go CELO/op-node/rollup/events_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d405883d3cce648691529c237006ce7641f3f9d6 --- /dev/null +++ CELO/op-node/rollup/events_test.go @@ -0,0 +1,50 @@ +package rollup + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +type TestEvent struct{} + +func (ev TestEvent) String() string { + return "X" +} + +func TestSynchronousDerivers_OnEvent(t *testing.T) { + result := "" + a := DeriverFunc(func(ev Event) { + result += fmt.Sprintf("A:%s\n", ev) + }) + b := DeriverFunc(func(ev Event) { + result += fmt.Sprintf("B:%s\n", ev) + }) + c := DeriverFunc(func(ev Event) { + result += fmt.Sprintf("C:%s\n", ev) + }) + + x := SynchronousDerivers{} + x.OnEvent(TestEvent{}) + require.Equal(t, "", result) + + x = SynchronousDerivers{a} + x.OnEvent(TestEvent{}) + require.Equal(t, "A:X\n", result) + + result = "" + x = SynchronousDerivers{a, a} + x.OnEvent(TestEvent{}) + require.Equal(t, "A:X\nA:X\n", result) + + result = "" + x = SynchronousDerivers{a, b} + x.OnEvent(TestEvent{}) + require.Equal(t, "A:X\nB:X\n", result) + + result = "" + x = SynchronousDerivers{a, b, c} + x.OnEvent(TestEvent{}) + require.Equal(t, "A:X\nB:X\nC:X\n", result) +}
diff --git OP/op-node/rollup/finality/finalizer.go CELO/op-node/rollup/finality/finalizer.go index 100ed96bb520096de9e43e0639eb3f7861dca00f..483110083d467724c5cb125a1c701ae4ed585fd4 100644 --- OP/op-node/rollup/finality/finalizer.go +++ CELO/op-node/rollup/finality/finalizer.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "sync" + "time"   "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-service/eth" )   @@ -34,7 +36,7 @@ // calcFinalityLookback calculates the default finality lookback based on DA challenge window if plasma // mode is activated or L1 finality lookback. func calcFinalityLookback(cfg *rollup.Config) uint64 { - // in plasma mode the longest finality lookback is a commitment is challenged on the last block of + // in alt-da mode the longest finality lookback is a commitment is challenged on the last block of // the challenge window in which case it will be both challenge + resolve window. if cfg.PlasmaEnabled() { lkb := cfg.PlasmaConfig.DAChallengeWindow + cfg.PlasmaConfig.DAResolveWindow + 1 @@ -68,9 +70,16 @@ mu sync.Mutex   log log.Logger   + ctx context.Context + + emitter rollup.EventEmitter + // finalizedL1 is the currently perceived finalized L1 block. // This may be ahead of the current traversed origin when syncing. finalizedL1 eth.L1BlockRef + + // lastFinalizedL2 maintains how far we finalized, so we don't have to emit re-attempts. + lastFinalizedL2 eth.L2BlockRef   // triedFinalizeAt tracks at which L1 block number we last tried to finalize during sync. triedFinalizeAt uint64 @@ -82,20 +91,19 @@ // Maximum amount of L2 blocks to store in finalityData. finalityLookback uint64   l1Fetcher FinalizerL1Interface - - ec FinalizerEngine }   -func NewFinalizer(log log.Logger, cfg *rollup.Config, l1Fetcher FinalizerL1Interface, ec FinalizerEngine) *Finalizer { +func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fetcher FinalizerL1Interface, emitter rollup.EventEmitter) *Finalizer { lookback := calcFinalityLookback(cfg) return &Finalizer{ + ctx: ctx, log: log, finalizedL1: eth.L1BlockRef{}, triedFinalizeAt: 0, finalityData: make([]FinalityData, 0, lookback), finalityLookback: lookback, l1Fetcher: l1Fetcher, - ec: ec, + emitter: emitter, } }   @@ -108,8 +116,39 @@ out = fi.finalizedL1 return }   -// Finalize applies a L1 finality signal, without any fork-choice or L2 state changes. -func (fi *Finalizer) Finalize(ctx context.Context, l1Origin eth.L1BlockRef) { +type FinalizeL1Event struct { + FinalizedL1 eth.L1BlockRef +} + +func (ev FinalizeL1Event) String() string { + return "finalized-l1" +} + +type TryFinalizeEvent struct{} + +func (ev TryFinalizeEvent) String() string { + return "try-finalize" +} + +func (fi *Finalizer) OnEvent(ev rollup.Event) { + switch x := ev.(type) { + case FinalizeL1Event: + fi.onL1Finalized(x.FinalizedL1) + case engine.SafeDerivedEvent: + fi.onDerivedSafeBlock(x.Safe, x.DerivedFrom) + case derive.DeriverIdleEvent: + fi.onDerivationIdle(x.Origin) + case rollup.ResetEvent: + fi.onReset() + case TryFinalizeEvent: + fi.tryFinalize() + case engine.ForkchoiceUpdateEvent: + fi.lastFinalizedL2 = x.FinalizedL2Head + } +} + +// onL1Finalized applies a L1 finality signal +func (fi *Finalizer) onL1Finalized(l1Origin eth.L1BlockRef) { fi.mu.Lock() defer fi.mu.Unlock() prevFinalizedL1 := fi.finalizedL1 @@ -127,13 +166,11 @@ // remember the L1 finalization signal fi.finalizedL1 = l1Origin }   - // remnant of finality in EngineQueue: the finalization work does not inherit a context from the caller. - if err := fi.tryFinalize(ctx); err != nil { - fi.log.Warn("received L1 finalization signal, but was unable to determine and apply L2 finality", "err", err) - } + // when the L1 change we can suggest to try to finalize, as the pre-condition for L2 finality has now changed + fi.emitter.Emit(TryFinalizeEvent{}) }   -// OnDerivationL1End is called when a L1 block has been fully exhausted (i.e. no more L2 blocks to derive from). +// onDerivationIdle is called when the pipeline is exhausted of new data (i.e. no more L2 blocks to derive from). // // Since finality applies to all L2 blocks fully derived from the same block, // it optimal to only check after the derivation from the L1 block has been exhausted. @@ -141,24 +178,27 @@ // // This will look at what has been buffered so far, // sanity-check we are on the finalizing L1 chain, // and finalize any L2 blocks that were fully derived from known finalized L1 blocks. -func (fi *Finalizer) OnDerivationL1End(ctx context.Context, derivedFrom eth.L1BlockRef) error { +func (fi *Finalizer) onDerivationIdle(derivedFrom eth.L1BlockRef) { fi.mu.Lock() defer fi.mu.Unlock() if fi.finalizedL1 == (eth.L1BlockRef{}) { - return nil // if no L1 information is finalized yet, then skip this + return // if no L1 information is finalized yet, then skip this } // If we recently tried finalizing, then don't try again just yet, but traverse more of L1 first. if fi.triedFinalizeAt != 0 && derivedFrom.Number <= fi.triedFinalizeAt+finalityDelay { - return nil + return } - fi.log.Info("processing L1 finality information", "l1_finalized", fi.finalizedL1, "derived_from", derivedFrom, "previous", fi.triedFinalizeAt) + fi.log.Debug("processing L1 finality information", "l1_finalized", fi.finalizedL1, "derived_from", derivedFrom, "previous", fi.triedFinalizeAt) fi.triedFinalizeAt = derivedFrom.Number - return fi.tryFinalize(ctx) + fi.emitter.Emit(TryFinalizeEvent{}) }   -func (fi *Finalizer) tryFinalize(ctx context.Context) error { - // default to keep the same finalized block - finalizedL2 := fi.ec.Finalized() +func (fi *Finalizer) tryFinalize() { + fi.mu.Lock() + defer fi.mu.Unlock() + + // overwritten if we finalize + finalizedL2 := fi.lastFinalizedL2 // may be zeroed if nothing was finalized since startup. var finalizedDerivedFrom eth.BlockID // go through the latest inclusion data, and find the last L2 block that was derived from a finalized L1 block for _, fd := range fi.finalityData { @@ -169,37 +209,41 @@ // keep iterating, there may be later L2 blocks that can also be finalized } } if finalizedDerivedFrom != (eth.BlockID{}) { + ctx, cancel := context.WithTimeout(fi.ctx, time.Second*10) + defer cancel() // Sanity check the finality signal of L1. // Even though the signal is trusted and we do the below check also, // the signal itself has to be canonical to proceed. // TODO(#10724): This check could be removed if the finality signal is fully trusted, and if tests were more flexible for this case. signalRef, err := fi.l1Fetcher.L1BlockRefByNumber(ctx, fi.finalizedL1.Number) if err != nil { - return derive.NewTemporaryError(fmt.Errorf("failed to check if on finalizing L1 chain, could not fetch block %d: %w", fi.finalizedL1.Number, err)) + fi.emitter.Emit(rollup.L1TemporaryErrorEvent{Err: fmt.Errorf("failed to check if on finalizing L1 chain, could not fetch block %d: %w", fi.finalizedL1.Number, err)}) + return } if signalRef.Hash != fi.finalizedL1.Hash { - return derive.NewResetError(fmt.Errorf("need to reset, we assumed %s is finalized, but canonical chain is %s", fi.finalizedL1, signalRef)) + fi.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("need to reset, we assumed %s is finalized, but canonical chain is %s", fi.finalizedL1, signalRef)}) + return }   // Sanity check we are indeed on the finalizing chain, and not stuck on something else. // We assume that the block-by-number query is consistent with the previously received finalized chain signal derivedRef, err := fi.l1Fetcher.L1BlockRefByNumber(ctx, finalizedDerivedFrom.Number) if err != nil { - return derive.NewTemporaryError(fmt.Errorf("failed to check if on finalizing L1 chain, could not fetch block %d: %w", finalizedDerivedFrom.Number, err)) + fi.emitter.Emit(rollup.L1TemporaryErrorEvent{Err: fmt.Errorf("failed to check if on finalizing L1 chain, could not fetch block %d: %w", finalizedDerivedFrom.Number, err)}) + return } if derivedRef.Hash != finalizedDerivedFrom.Hash { - return derive.NewResetError(fmt.Errorf("need to reset, we are on %s, not on the finalizing L1 chain %s (towards %s)", - finalizedDerivedFrom, derivedRef, fi.finalizedL1)) + fi.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("need to reset, we are on %s, not on the finalizing L1 chain %s (towards %s)", + finalizedDerivedFrom, derivedRef, fi.finalizedL1)}) + return } - - fi.ec.SetFinalizedHead(finalizedL2) + fi.emitter.Emit(engine.PromoteFinalizedEvent{Ref: finalizedL2}) } - return nil }   -// PostProcessSafeL2 buffers the L1 block the safe head was fully derived from, +// onDerivedSafeBlock buffers the L1 block the safe head was fully derived from, // to finalize it once the derived-from L1 block, or a later L1 block, finalizes. -func (fi *Finalizer) PostProcessSafeL2(l2Safe eth.L2BlockRef, derivedFrom eth.L1BlockRef) { +func (fi *Finalizer) onDerivedSafeBlock(l2Safe eth.L2BlockRef, derivedFrom eth.L1BlockRef) { fi.mu.Lock() defer fi.mu.Unlock() // remember the last L2 block that we fully derived from the given finality data @@ -225,9 +269,9 @@ } } }   -// Reset clears the recent history of safe-L2 blocks used for finalization, +// onReset clears the recent history of safe-L2 blocks used for finalization, // to avoid finalizing any reorged-out L2 blocks. -func (fi *Finalizer) Reset() { +func (fi *Finalizer) onReset() { fi.mu.Lock() defer fi.mu.Unlock() fi.finalityData = fi.finalityData[:0]
diff --git OP/op-node/rollup/finality/finalizer_test.go CELO/op-node/rollup/finality/finalizer_test.go index e577efe1952af9c6cf8b980a38fc656b833cf9a1..d4dc76fb4ca55c2fc1577b78ae4e98f0763a1094 100644 --- OP/op-node/rollup/finality/finalizer_test.go +++ CELO/op-node/rollup/finality/finalizer_test.go @@ -13,24 +13,11 @@ "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testutils" ) - -type fakeEngine struct { - finalized eth.L2BlockRef -} - -func (f *fakeEngine) Finalized() eth.L2BlockRef { - return f.finalized -} - -func (f *fakeEngine) SetFinalizedHead(ref eth.L2BlockRef) { - f.finalized = ref -} - -var _ FinalizerEngine = (*fakeEngine)(nil)   func TestEngineQueue_Finalize(t *testing.T) { rng := rand.New(rand.NewSource(1234)) @@ -80,12 +67,12 @@ Number: refG.Number + 1, ParentHash: refG.Hash, Time: refG.Time + l1Time, } - refI := eth.L1BlockRef{ - Hash: testutils.RandomHash(rng), - Number: refH.Number + 1, - ParentHash: refH.Hash, - Time: refH.Time + l1Time, - } + //refI := eth.L1BlockRef{ + // Hash: testutils.RandomHash(rng), + // Number: refH.Number + 1, + // ParentHash: refH.Hash, + // Time: refH.Time + l1Time, + //}   refA0 := eth.L2BlockRef{ Hash: testutils.RandomHash(rng), @@ -203,22 +190,29 @@ defer l1F.AssertExpectations(t) l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil)   - ec := &fakeEngine{} - ec.SetFinalizedHead(refA1) - - fi := NewFinalizer(logger, &rollup.Config{}, l1F, ec) + emitter := &testutils.MockEmitter{} + fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)   // now say C1 was included in D and became the new safe head - fi.PostProcessSafeL2(refC1, refD) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refD)) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1, DerivedFrom: refD}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refD}) + emitter.AssertExpectations(t)   // now say D0 was included in E and became the new safe head - fi.PostProcessSafeL2(refD0, refE) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refE)) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refD0, DerivedFrom: refE}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refE}) + emitter.AssertExpectations(t) + + // Let's finalize D from which we fully derived C1, but not D0 + // This will trigger an attempt of L2 finalization. + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(FinalizeL1Event{FinalizedL1: refD}) + emitter.AssertExpectations(t)   - // let's finalize D from which we fully derived C1, but not D0 - fi.Finalize(context.Background(), refD) - require.Equal(t, refC1, ec.Finalized(), "C1 was included in finalized D, and should now be finalized, as finality signal is instantly picked up") + // C1 was included in finalized D, and should now be finalized + emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refC1}) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) })   // Finality signal is received, but couldn't immediately be checked @@ -230,25 +224,37 @@ l1F.ExpectL1BlockRefByNumber(refD.Number, refD, errors.New("fake error")) l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) // to check finality signal l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) // to check what was derived from (same in this case)   - ec := &fakeEngine{} - ec.SetFinalizedHead(refA1) - - fi := NewFinalizer(logger, &rollup.Config{}, l1F, ec) + emitter := &testutils.MockEmitter{} + fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)   // now say C1 was included in D and became the new safe head - fi.PostProcessSafeL2(refC1, refD) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refD)) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1, DerivedFrom: refD}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refD}) + emitter.AssertExpectations(t)   // now say D0 was included in E and became the new safe head - fi.PostProcessSafeL2(refD0, refE) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refE)) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refD0, DerivedFrom: refE}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refE}) + emitter.AssertExpectations(t)   // let's finalize D from which we fully derived C1, but not D0 - fi.Finalize(context.Background(), refD) - require.Equal(t, refA1, ec.Finalized(), "C1 was included in finalized D, but finality could not be verified yet, due to temporary test error") + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(FinalizeL1Event{FinalizedL1: refD}) + emitter.AssertExpectations(t) + // C1 was included in finalized D, but finality could not be verified yet, due to temporary test error + emitter.ExpectOnceType("L1TemporaryErrorEvent") + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t)   - require.NoError(t, fi.OnDerivationL1End(context.Background(), refF)) - require.Equal(t, refC1, ec.Finalized(), "C1 was included in finalized D, and should now be finalized, as check can succeed when revisited") + // upon the next signal we should schedule a finalization re-attempt + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refF}) + emitter.AssertExpectations(t) + + // C1 was included in finalized D, and should now be finalized, as check can succeed when revisited + emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refC1}) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) })   // Test that finality progression can repeat a few times. @@ -257,43 +263,80 @@ logger := testlog.Logger(t, log.LevelInfo) l1F := &testutils.MockL1Source{} defer l1F.AssertExpectations(t)   + emitter := &testutils.MockEmitter{} + fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter) + + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1, DerivedFrom: refD}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refD}) + emitter.AssertExpectations(t) + + fi.OnEvent(engine.SafeDerivedEvent{Safe: refD0, DerivedFrom: refE}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refE}) + emitter.AssertExpectations(t) + + // L1 finality signal will trigger L2 finality attempt + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(FinalizeL1Event{FinalizedL1: refD}) + emitter.AssertExpectations(t) + + // C1 was included in D, and should be finalized now + emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refC1}) l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) + l1F.AssertExpectations(t) + + // Another L1 finality event, trigger L2 finality attempt again + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(FinalizeL1Event{FinalizedL1: refE}) + emitter.AssertExpectations(t) + + // D0 was included in E, and should be finalized now + emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refD0}) l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil) l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil) - l1F.ExpectL1BlockRefByNumber(refH.Number, refH, nil) - l1F.ExpectL1BlockRefByNumber(refH.Number, refH, nil) - - ec := &fakeEngine{} - ec.SetFinalizedHead(refA1) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) + l1F.AssertExpectations(t)   - fi := NewFinalizer(logger, &rollup.Config{}, l1F, ec) + // D0 is still there in the buffer, and may be finalized again, if it were not for the latest forkchoice update. + fi.OnEvent(engine.ForkchoiceUpdateEvent{FinalizedL2Head: refD0}) + emitter.AssertExpectations(t) // should trigger no events   - fi.PostProcessSafeL2(refC1, refD) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refD)) + // we expect a finality attempt, since we have not idled on something yet + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refG}) + emitter.AssertExpectations(t)   - fi.PostProcessSafeL2(refD0, refE) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refE)) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refD1, DerivedFrom: refH}) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refE0, DerivedFrom: refH}) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refE1, DerivedFrom: refH}) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refF0, DerivedFrom: refH}) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refF1, DerivedFrom: refH}) + emitter.AssertExpectations(t) // above updates add data, but no attempt is made until idle or L1 signal   - fi.Finalize(context.Background(), refD) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refF)) - require.Equal(t, refC1, ec.Finalized(), "C1 was included in D, and should be finalized now") + // We recently finalized already, and there is no new L1 finality data + fi.OnEvent(derive.DeriverIdleEvent{Origin: refH}) + emitter.AssertExpectations(t)   - fi.Finalize(context.Background(), refE) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refG)) - require.Equal(t, refD0, ec.Finalized(), "D0 was included in E, and should be finalized now") + // D1-F1 were included in L1 blocks that have not been finalized yet. + // D0 is known to be finalized already. + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t)   - fi.PostProcessSafeL2(refD1, refH) - fi.PostProcessSafeL2(refE0, refH) - fi.PostProcessSafeL2(refE1, refH) - fi.PostProcessSafeL2(refF0, refH) - fi.PostProcessSafeL2(refF1, refH) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refH)) - require.Equal(t, refD0, ec.Finalized(), "D1-F1 were included in L1 blocks that have not been finalized yet") + // Now L1 block H is actually finalized, and we can proceed with another attempt + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(FinalizeL1Event{FinalizedL1: refH}) + emitter.AssertExpectations(t)   - fi.Finalize(context.Background(), refH) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refI)) - require.Equal(t, refF1, ec.Finalized(), "F1 should be finalized now") + // F1 should be finalized now, since it was included in H + emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refF1}) + l1F.ExpectL1BlockRefByNumber(refH.Number, refH, nil) + l1F.ExpectL1BlockRefByNumber(refH.Number, refH, nil) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) + l1F.AssertExpectations(t) })   // In this test the finality signal is for a block more than @@ -305,22 +348,28 @@ defer l1F.AssertExpectations(t) l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) // check the signal l1F.ExpectL1BlockRefByNumber(refC.Number, refC, nil) // check what we derived the L2 block from   - ec := &fakeEngine{} - ec.SetFinalizedHead(refA1) - - fi := NewFinalizer(logger, &rollup.Config{}, l1F, ec) + emitter := &testutils.MockEmitter{} + fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)   // now say B1 was included in C and became the new safe head - fi.PostProcessSafeL2(refB1, refC) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refC)) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refB1, DerivedFrom: refC}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refC}) + emitter.AssertExpectations(t)   // now say C0 was included in E and became the new safe head - fi.PostProcessSafeL2(refC0, refE) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refE)) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC0, DerivedFrom: refE}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refE}) + emitter.AssertExpectations(t)   // let's finalize D, from which we fully derived B1, but not C0 (referenced L1 origin in L2 block != inclusion of L2 block in L1 chain) - fi.Finalize(context.Background(), refD) - require.Equal(t, refB1, ec.Finalized(), "B1 was included in finalized D, and should now be finalized") + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(FinalizeL1Event{FinalizedL1: refD}) + emitter.AssertExpectations(t) + + // B1 was included in finalized D, and should now be finalized + emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refB1}) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) })   // Test that reorg race condition is handled. @@ -335,14 +384,13 @@ l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) // shows reorg to OnDerivationL1End attempt l1F.ExpectL1BlockRefByNumber(refF.Number, refF, nil) // check signal l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil) // post-reorg   - ec := &fakeEngine{} - ec.SetFinalizedHead(refA1) - - fi := NewFinalizer(logger, &rollup.Config{}, l1F, ec) + emitter := &testutils.MockEmitter{} + fi := NewFinalizer(context.Background(), logger, &rollup.Config{}, l1F, emitter)   // now say B1 was included in C and became the new safe head - fi.PostProcessSafeL2(refB1, refC) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refC)) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refB1, DerivedFrom: refC}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refC}) + emitter.AssertExpectations(t)   // temporary fork of the L1, and derived safe L2 blocks from. refC0Alt := eth.L2BlockRef{ @@ -367,34 +415,56 @@ Number: refC.Number + 1, ParentHash: refC.Hash, Time: refC.Time + l1Time, } - fi.PostProcessSafeL2(refC0Alt, refDAlt) - fi.PostProcessSafeL2(refC1Alt, refDAlt) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC0Alt, DerivedFrom: refDAlt}) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1Alt, DerivedFrom: refDAlt})   // We get an early finality signal for F, of the chain that did not include refC0Alt and refC1Alt, // as L1 block F does not build on DAlt. // The finality signal was for a new chain, while derivation is on an old stale chain. // It should be detected that C0Alt and C1Alt cannot actually be finalized, // even though they are older than the latest finality signal. - fi.Finalize(context.Background(), refF) - require.Equal(t, refA1, ec.Finalized(), "cannot verify refC0Alt and refC1Alt, and refB1 is older and not checked") + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(FinalizeL1Event{FinalizedL1: refF}) + emitter.AssertExpectations(t) + // cannot verify refC0Alt and refC1Alt, and refB1 is older and not checked + emitter.ExpectOnceType("ResetEvent") + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) // no change in finality + // And process DAlt, still stuck on old chain. - require.ErrorIs(t, derive.ErrReset, fi.OnDerivationL1End(context.Background(), refDAlt)) - require.Equal(t, refA1, ec.Finalized(), "no new finalized L2 blocks after early finality signal with stale chain") + + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refDAlt}) + emitter.AssertExpectations(t) + // no new finalized L2 blocks after early finality signal with stale chain + emitter.ExpectOnceType("ResetEvent") + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) + // Now reset, because of the reset error + fi.OnEvent(rollup.ResetEvent{}) require.Equal(t, refF, fi.FinalizedL1(), "remember the new finality signal for later however") - // Now reset, because of the reset error - fi.Reset()   // And process the canonical chain, with empty block D (no post-processing of canonical C0 blocks yet) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refD)) + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refD}) + emitter.AssertExpectations(t) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) // no new finality   // Include C0 in E - fi.PostProcessSafeL2(refC0, refE) - require.NoError(t, fi.OnDerivationL1End(context.Background(), refE)) - // Due to the "finalityDelay" we don't repeat finality checks shortly after one another. - require.Equal(t, refA1, ec.Finalized()) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC0, DerivedFrom: refE}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refE}) + // Due to the "finalityDelay" we don't repeat finality checks shortly after one another, + // and don't expect a finality attempt. + emitter.AssertExpectations(t) + // if we reset the attempt, then we can finalize however. fi.triedFinalizeAt = 0 - require.NoError(t, fi.OnDerivationL1End(context.Background(), refE)) - require.Equal(t, refC0, ec.Finalized()) + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refE}) + emitter.AssertExpectations(t) + emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refC0}) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) }) }
diff --git OP/op-node/rollup/finality/plasma.go CELO/op-node/rollup/finality/plasma.go index e7826cda71832da86c5793041ac242a27c8a32b8..e62c96169c5ab57a09b9a26ca861ff5b44f58020 100644 --- OP/op-node/rollup/finality/plasma.go +++ CELO/op-node/rollup/finality/plasma.go @@ -26,17 +26,17 @@ *Finalizer backend PlasmaBackend }   -func NewPlasmaFinalizer(log log.Logger, cfg *rollup.Config, - l1Fetcher FinalizerL1Interface, ec FinalizerEngine, +func NewPlasmaFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, + l1Fetcher FinalizerL1Interface, emitter rollup.EventEmitter, backend PlasmaBackend) *PlasmaFinalizer {   - inner := NewFinalizer(log, cfg, l1Fetcher, ec) + inner := NewFinalizer(ctx, log, cfg, l1Fetcher, emitter)   - // In plasma mode, the finalization signal is proxied through the plasma manager. + // In alt-da mode, the finalization signal is proxied through the plasma manager. // Finality signal will come from the DA contract or L1 finality whichever is last. // The plasma module will then call the inner.Finalize function when applicable. backend.OnFinalizedHeadSignal(func(ref eth.L1BlockRef) { - inner.Finalize(context.Background(), ref) // plasma backend context passing can be improved + inner.OnEvent(FinalizeL1Event{FinalizedL1: ref}) })   return &PlasmaFinalizer{ @@ -45,6 +45,11 @@ backend: backend, } }   -func (fi *PlasmaFinalizer) Finalize(ctx context.Context, l1Origin eth.L1BlockRef) { - fi.backend.Finalize(l1Origin) +func (fi *PlasmaFinalizer) OnEvent(ev rollup.Event) { + switch x := ev.(type) { + case FinalizeL1Event: + fi.backend.Finalize(x.FinalizedL1) + default: + fi.Finalizer.OnEvent(ev) + } }
diff --git OP/op-node/rollup/finality/plasma_test.go CELO/op-node/rollup/finality/plasma_test.go index 4c1ecc00acccb7c592ee95e4e592a6b30d1407ef..291fa026dcb49771889ae388ef65f9cedad0ff16 100644 --- OP/op-node/rollup/finality/plasma_test.go +++ CELO/op-node/rollup/finality/plasma_test.go @@ -11,6 +11,8 @@ "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/rollup/engine" plasma "github.com/ethereum-optimism/optimism/op-plasma" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" @@ -83,9 +85,6 @@ L1Origin: refA.ID(), SequenceNumber: 1, }   - ec := &fakeEngine{} - ec.SetFinalizedHead(refA1) - // Simulate plasma finality by waiting for the finalized-inclusion // of a commitment to turn into undisputed finalized data. commitmentInclusionFinalized := eth.L1BlockRef{} @@ -95,7 +94,9 @@ commitmentInclusionFinalized = ref }, forwardTo: nil, } - fi := NewPlasmaFinalizer(logger, cfg, l1F, ec, plasmaBackend) + + emitter := &testutils.MockEmitter{} + fi := NewPlasmaFinalizer(context.Background(), logger, cfg, l1F, emitter, plasmaBackend) require.NotNil(t, plasmaBackend.forwardTo, "plasma backend must have access to underlying standard finalizer")   require.Equal(t, expFinalityLookback, cap(fi.finalityData)) @@ -107,7 +108,9 @@ // advance over 200 l1 origins each time incrementing new l2 safe heads // and post processing. for i := uint64(0); i < 200; i++ { if i == 10 { // finalize a L1 commitment - fi.Finalize(context.Background(), l1parent) + fi.OnEvent(FinalizeL1Event{FinalizedL1: l1parent}) + emitter.AssertExpectations(t) // no events emitted upon L1 finality + require.Equal(t, l1parent, commitmentInclusionFinalized, "plasma backend received L1 signal") }   previous := l1parent @@ -127,24 +130,56 @@ Time: l2parent.Time + cfg.BlockTime, L1Origin: previous.ID(), // reference previous origin, not the block the batch was included in SequenceNumber: j, } - fi.PostProcessSafeL2(l2parent, l1parent) + fi.OnEvent(engine.SafeDerivedEvent{Safe: l2parent, DerivedFrom: l1parent}) + emitter.AssertExpectations(t) } - require.NoError(t, fi.OnDerivationL1End(context.Background(), l1parent)) + // might trigger finalization attempt, if expired finality delay + emitter.ExpectMaybeRun(func(ev rollup.Event) { + require.IsType(t, TryFinalizeEvent{}, ev) + }) + fi.OnEvent(derive.DeriverIdleEvent{}) + emitter.AssertExpectations(t) + // clear expectations + emitter.Mock.ExpectedCalls = nil + + // no L2 finalize event, as no L1 finality signal has been forwarded by plasma backend yet + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) + + // Pretend to be the plasma backend, + // send the original finalization signal to the underlying finalizer, + // now that we are sure the commitment itself is not just finalized, + // but the referenced data cannot be disputed anymore. plasmaFinalization := commitmentInclusionFinalized.Number + cfg.PlasmaConfig.DAChallengeWindow - if i == plasmaFinalization { - // Pretend to be the plasma backend, - // send the original finalization signal to the underlying finalizer, - // now that we are sure the commitment itself is not just finalized, - // but the referenced data cannot be disputed anymore. + if commitmentInclusionFinalized != (eth.L1BlockRef{}) && l1parent.Number == plasmaFinalization { + // When the signal is forwarded, a finalization attempt will be scheduled + emitter.ExpectOnce(TryFinalizeEvent{}) plasmaBackend.forwardTo(commitmentInclusionFinalized) - } - // The next time OnDerivationL1End is called, after the finality signal was triggered by plasma backend, - // we should have a finalized L2 block. - // The L1 origin of the simulated L2 blocks lags 1 behind the block the L2 block is included in on L1. - // So to check the L2 finality progress, we check if the next L1 block after the L1 origin - // of the safe block matches that of the finalized L1 block. - if i == plasmaFinalization+1 { - require.Equal(t, plasmaFinalization, ec.Finalized().L1Origin.Number+1) + emitter.AssertExpectations(t) + require.Equal(t, commitmentInclusionFinalized, fi.finalizedL1, "finality signal now made its way in regular finalizer") + + // As soon as a finalization attempt is made, after the finality signal was triggered by plasma backend, + // we should get an attempt to get a finalized L2 block. + // In this test the L1 origin of the simulated L2 blocks lags 1 behind the block the L2 block is included in on L1. + // So to check the L2 finality progress, we check if the next L1 block after the L1 origin + // of the safe block matches that of the finalized L1 block. + l1F.ExpectL1BlockRefByNumber(commitmentInclusionFinalized.Number, commitmentInclusionFinalized, nil) + l1F.ExpectL1BlockRefByNumber(commitmentInclusionFinalized.Number, commitmentInclusionFinalized, nil) + var finalizedL2 eth.L2BlockRef + emitter.ExpectOnceRun(func(ev rollup.Event) { + if x, ok := ev.(engine.PromoteFinalizedEvent); ok { + finalizedL2 = x.Ref + } else { + t.Fatalf("expected L2 finalization, but got: %s", ev) + } + }) + fi.OnEvent(TryFinalizeEvent{}) + l1F.AssertExpectations(t) + emitter.AssertExpectations(t) + require.Equal(t, commitmentInclusionFinalized.Number, finalizedL2.L1Origin.Number+1) + // Confirm finalization, so there will be no repeats of the PromoteFinalizedEvent + fi.OnEvent(engine.ForkchoiceUpdateEvent{FinalizedL2Head: finalizedL2}) + emitter.AssertExpectations(t) } }
diff --git OP/op-node/rollup/synchronous.go CELO/op-node/rollup/synchronous.go new file mode 100644 index 0000000000000000000000000000000000000000..68943381e24eee966b9cd257aeeb559c457f513d --- /dev/null +++ CELO/op-node/rollup/synchronous.go @@ -0,0 +1,103 @@ +package rollup + +import ( + "context" + "io" + "sync" + + "github.com/ethereum/go-ethereum/log" +) + +// Don't queue up an endless number of events. +// At some point it's better to drop events and warn something is exploding the number of events. +const sanityEventLimit = 1000 + +// SynchronousEvents is a rollup.EventEmitter that a rollup.Deriver can emit events to. +// The events will be queued up, and can then be executed synchronously by calling the Drain function, +// which will apply all events to the root Deriver. +// New events may be queued up while events are being processed by the root rollup.Deriver. +type SynchronousEvents struct { + // The lock is no-op in FP execution, if running in synchronous FP-VM. + // This lock ensures that all emitted events are merged together correctly, + // if this util is used in a concurrent context. + evLock sync.Mutex + + events []Event + + log log.Logger + + ctx context.Context + + root Deriver +} + +func NewSynchronousEvents(log log.Logger, ctx context.Context, root Deriver) *SynchronousEvents { + return &SynchronousEvents{ + log: log, + ctx: ctx, + root: root, + } +} + +func (s *SynchronousEvents) Emit(event Event) { + s.evLock.Lock() + defer s.evLock.Unlock() + + if s.ctx.Err() != nil { + s.log.Warn("Ignoring emitted event during shutdown", "event", event) + return + } + + // sanity limit, never queue too many events + if len(s.events) >= sanityEventLimit { + s.log.Error("Something is very wrong, queued up too many events! Dropping event", "ev", event) + return + } + s.events = append(s.events, event) +} + +func (s *SynchronousEvents) Drain() error { + for { + if s.ctx.Err() != nil { + return s.ctx.Err() + } + if len(s.events) == 0 { + return nil + } + + s.evLock.Lock() + first := s.events[0] + s.events = s.events[1:] + s.evLock.Unlock() + + s.root.OnEvent(first) + } +} + +func (s *SynchronousEvents) DrainUntil(fn func(ev Event) bool, excl bool) error { + for { + if s.ctx.Err() != nil { + return s.ctx.Err() + } + if len(s.events) == 0 { + return io.EOF + } + + s.evLock.Lock() + first := s.events[0] + stop := fn(first) + if excl && stop { + s.evLock.Unlock() + return nil + } + s.events = s.events[1:] + s.evLock.Unlock() + + s.root.OnEvent(first) + if stop { + return nil + } + } +} + +var _ EventEmitter = (*SynchronousEvents)(nil)
diff --git OP/op-node/rollup/synchronous_test.go CELO/op-node/rollup/synchronous_test.go new file mode 100644 index 0000000000000000000000000000000000000000..191b4f4246a2097d47bc5fb838ae1cd8237cf98d --- /dev/null +++ CELO/op-node/rollup/synchronous_test.go @@ -0,0 +1,88 @@ +package rollup + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +func TestSynchronousEvents(t *testing.T) { + logger := testlog.Logger(t, log.LevelError) + ctx, cancel := context.WithCancel(context.Background()) + count := 0 + deriver := DeriverFunc(func(ev Event) { + count += 1 + }) + syncEv := NewSynchronousEvents(logger, ctx, deriver) + require.NoError(t, syncEv.Drain(), "can drain, even if empty") + + syncEv.Emit(TestEvent{}) + require.Equal(t, 0, count, "no processing yet, queued event") + require.NoError(t, syncEv.Drain()) + require.Equal(t, 1, count, "processed event") + + syncEv.Emit(TestEvent{}) + syncEv.Emit(TestEvent{}) + require.Equal(t, 1, count, "no processing yet, queued events") + require.NoError(t, syncEv.Drain()) + require.Equal(t, 3, count, "processed events") + + cancel() + syncEv.Emit(TestEvent{}) + require.Equal(t, ctx.Err(), syncEv.Drain(), "no draining after close") + require.Equal(t, 3, count, "didn't process event after trigger close") +} + +func TestSynchronousEventsSanityLimit(t *testing.T) { + logger := testlog.Logger(t, log.LevelError) + count := 0 + deriver := DeriverFunc(func(ev Event) { + count += 1 + }) + syncEv := NewSynchronousEvents(logger, context.Background(), deriver) + // emit 1 too many events + for i := 0; i < sanityEventLimit+1; i++ { + syncEv.Emit(TestEvent{}) + } + require.NoError(t, syncEv.Drain()) + require.Equal(t, sanityEventLimit, count, "processed all non-dropped events") + + syncEv.Emit(TestEvent{}) + require.NoError(t, syncEv.Drain()) + require.Equal(t, sanityEventLimit+1, count, "back to normal after drain") +} + +type CyclicEvent struct { + Count int +} + +func (ev CyclicEvent) String() string { + return "cyclic-event" +} + +func TestSynchronousCyclic(t *testing.T) { + logger := testlog.Logger(t, log.LevelError) + var emitter EventEmitter + result := false + deriver := DeriverFunc(func(ev Event) { + logger.Info("received event", "event", ev) + switch x := ev.(type) { + case CyclicEvent: + if x.Count < 10 { + emitter.Emit(CyclicEvent{Count: x.Count + 1}) + } else { + result = true + } + } + }) + syncEv := NewSynchronousEvents(logger, context.Background(), deriver) + emitter = syncEv + syncEv.Emit(CyclicEvent{Count: 0}) + require.NoError(t, syncEv.Drain()) + require.True(t, result, "expecting event processing to fully recurse") +}
diff --git OP/op-node/rollup/types.go CELO/op-node/rollup/types.go index 816181687567a074ad8aeb4666fa6e4c1b82d372..aa9dc1a3220f1f030753e6a5394effdebb21ac9c 100644 --- OP/op-node/rollup/types.go +++ CELO/op-node/rollup/types.go @@ -54,10 +54,10 @@ // L1 DataAvailabilityChallenge contract proxy address DAChallengeAddress common.Address `json:"da_challenge_contract_address,omitempty"` // CommitmentType specifies which commitment type can be used. Defaults to Keccak (type 0) if not present CommitmentType string `json:"da_commitment_type"` - // DA challenge window value set on the DAC contract. Used in plasma mode + // DA challenge window value set on the DAC contract. Used in alt-da mode // to compute when a commitment can no longer be challenged. DAChallengeWindow uint64 `json:"da_challenge_window"` - // DA resolve window value set on the DAC contract. Used in plasma mode + // DA resolve window value set on the DAC contract. Used in alt-da mode // to compute when a challenge expires and trigger a reorg if needed. DAResolveWindow uint64 `json:"da_resolve_window"` } @@ -92,6 +92,7 @@ // a pre-mainnet Bedrock change that addresses findings of the Sherlock contest related to deposit attributes. // "Regolith" is the loose deposited rock that sits on top of Bedrock. // Active if RegolithTime != nil && L2 block timestamp >= *RegolithTime, inactive otherwise. RegolithTime *uint64 `json:"regolith_time,omitempty"` + Cel2Time *uint64 `json:"cel2_time,omitempty"`   // CanyonTime sets the activation time of the Canyon network upgrade. // Active if CanyonTime != nil && L2 block timestamp >= *CanyonTime, inactive otherwise. @@ -132,15 +133,15 @@ // L1 DataAvailabilityChallenge contract proxy address LegacyDAChallengeAddress common.Address `json:"da_challenge_contract_address,omitempty"`   - // DA challenge window value set on the DAC contract. Used in plasma mode + // DA challenge window value set on the DAC contract. Used in alt-da mode // to compute when a commitment can no longer be challenged. LegacyDAChallengeWindow uint64 `json:"da_challenge_window,omitempty"`   - // DA resolve window value set on the DAC contract. Used in plasma mode + // DA resolve window value set on the DAC contract. Used in alt-da mode // to compute when a challenge expires and trigger a reorg if needed. LegacyDAResolveWindow uint64 `json:"da_resolve_window,omitempty"`   - // LegacyUsePlasma is activated when the chain is in plasma mode. + // LegacyUsePlasma is activated when the chain is in alt-da mode. LegacyUsePlasma bool `json:"use_plasma,omitempty"` }   @@ -326,7 +327,7 @@ return nil }   -// validatePlasmaConfig checks the two approaches to configuring plasma mode. +// validatePlasmaConfig checks the two approaches to configuring alt-da mode. // If the legacy values are set, they are copied to the new location. If both are set, they are check for consistency. func validatePlasmaConfig(cfg *Config) error { if cfg.LegacyUsePlasma && cfg.PlasmaConfig == nil { @@ -522,7 +523,7 @@ return c.PlasmaConfig != nil }   // SyncLookback computes the number of blocks to walk back in order to find the correct L1 origin. -// In plasma mode longest possible window is challenge + resolve windows. +// In alt-da mode longest possible window is challenge + resolve windows. func (c *Config) SyncLookback() uint64 { if c.PlasmaEnabled() { if win := (c.PlasmaConfig.DAChallengeWindow + c.PlasmaConfig.DAResolveWindow); win > c.SeqWindowSize { @@ -567,7 +568,7 @@ banner += fmt.Sprintf(" - Interop: %s\n", fmtForkTimeOrUnset(c.InteropTime)) // Report the protocol version banner += fmt.Sprintf("Node supports up to OP-Stack Protocol Version: %s\n", OPStackSupport) if c.PlasmaConfig != nil { - banner += fmt.Sprintf("Node supports Plasma Mode with CommitmentType %v\n", c.PlasmaConfig.CommitmentType) + banner += fmt.Sprintf("Node supports Alt-DA Mode with CommitmentType %v\n", c.PlasmaConfig.CommitmentType) } return banner } @@ -599,6 +600,7 @@ "ecotone_time", fmtForkTimeOrUnset(c.EcotoneTime), "fjord_time", fmtForkTimeOrUnset(c.FjordTime), "interop_time", fmtForkTimeOrUnset(c.InteropTime), "plasma_mode", c.PlasmaConfig != nil, + "cel2_time", fmtForkTimeOrUnset(c.Cel2Time), ) }
diff --git OP/ops-bedrock/Dockerfile.l2 CELO/ops-bedrock/Dockerfile.l2 index 976ab27498598d48c61548f63c70958a1d8f3cac..34e545be69a7c17312e723c1b735d01f0580e9a6 100644 --- OP/ops-bedrock/Dockerfile.l2 +++ CELO/ops-bedrock/Dockerfile.l2 @@ -1,4 +1,4 @@ -FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:optimism +FROM --platform=linux/amd64 us-west1-docker.pkg.dev/blockchaintestsglobaltestnet/dev-images/op-geth@sha256:fab76a990c21271419a40dfe5d28e30905869183b18ee9e6f711fe562365bc8e   RUN apk add --no-cache jq
(deleted)
+0
-3
diff --git OP/proxyd/.gitignore CELO/proxyd/.gitignore deleted file mode 100644 index 65e6a826f682d6b1f4e8b188cdcecfe7e463ada3..0000000000000000000000000000000000000000 --- OP/proxyd/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -bin - -config.toml
(deleted)
+0
-252
diff --git OP/proxyd/CHANGELOG.md CELO/proxyd/CHANGELOG.md deleted file mode 100644 index dd78bfe3c8038e86dd00d6992af6f2a2601ffa4b..0000000000000000000000000000000000000000 --- OP/proxyd/CHANGELOG.md +++ /dev/null @@ -1,252 +0,0 @@ -# @eth-optimism/proxyd - -## 3.14.1 - -### Patch Changes - -- 5602deec7: chore(deps): bump github.com/prometheus/client_golang from 1.11.0 to 1.11.1 in /proxyd -- 6b3cf2070: Remove useless logging - -## 3.14.0 - -### Minor Changes - -- 9cc39bcfa: Add support for global method override rate limit -- 30db32862: Include nonce in sender rate limit - -### Patch Changes - -- b9bb1a98a: proxyd: Add req_id to log - -## 3.13.0 - -### Minor Changes - -- 6de891d3b: Add sender-based rate limiter - -## 3.12.0 - -### Minor Changes - -- e9f2c701: Allow disabling backend rate limiter -- ca45a85e: Support pattern matching in exempt origins/user agents -- f4faa44c: adds server.log_level config - -## 3.11.0 - -### Minor Changes - -- b3c5eeec: Fixed JSON-RPC 2.0 specification compliance by adding the optional data field on an RPCError -- 01ae6625: Adds new Redis rate limiter - -## 3.10.2 - -### Patch Changes - -- 6bb35fd8: Add customizable whitelist error -- 7121648c: Batch metrics and max batch size - -## 3.10.1 - -### Patch Changes - -- b82a8f48: Add logging for origin and remote IP' -- 1bf9559c: Carry over custom limit message in batches - -## 3.10.0 - -### Minor Changes - -- 157ccc84: Support per-method rate limiting - -## 3.9.1 - -### Patch Changes - -- dc4f6a06: Add logging/metrics - -## 3.9.0 - -### Minor Changes - -- b6f4bfcf: Add frontend rate limiting - -### Patch Changes - -- 406a4fce: Unwrap single RPC batches -- 915f3b28: Parameterize full RPC request logging - -## 3.8.9 - -### Patch Changes - -- 063c55cf: Use canned response for eth_accounts - -## 3.8.8 - -### Patch Changes - -- 58dc7adc: Improve robustness against unexpected JSON-RPC from upstream -- 552cd641: Fix concurrent write panic in WS - -## 3.8.7 - -### Patch Changes - -- 6f458607: Bump go-ethereum to 1.10.17 - -## 3.8.6 - -### Patch Changes - -- d79d40c4: proxyd: Proxy requests using batch JSON-RPC - -## 3.8.5 - -### Patch Changes - -- 2a062b11: proxyd: Log ssanitized RPC requests -- d9f058ce: proxyd: Reduced RPC request logging -- a4bfd9e7: proxyd: Limit the number of concurrent RPCs to backends - -## 3.8.4 - -### Patch Changes - -- 08329ba2: proxyd: Record redis cache operation latency -- ae112021: proxyd: Request-scoped context for fast batch RPC short-circuiting - -## 3.8.3 - -### Patch Changes - -- 160f4c3d: Update docker image to use golang 1.18.0 - -## 3.8.2 - -### Patch Changes - -- ae18cea1: Don't hit Redis when the out of service interval is zero - -## 3.8.1 - -### Patch Changes - -- acf7dbd5: Update to go-ethereum v1.10.16 - -## 3.8.0 - -### Minor Changes - -- 527448bb: Handle nil responses better - -## 3.7.0 - -### Minor Changes - -- 3c2926b1: Add debug cache status header to proxyd responses - -## 3.6.0 - -### Minor Changes - -- 096c5f20: proxyd: Allow cached RPCs to be evicted by redis -- 71d64834: Add caching for block-dependent RPCs -- fd2e1523: proxyd: Cache block-dependent RPCs -- 1760613c: Add integration tests and batching - -## 3.5.0 - -### Minor Changes - -- 025a3c0d: Add request/response payload size metrics to proxyd -- daf8db0b: cache immutable RPC responses in proxyd -- 8aa89bf3: Add X-Forwarded-For header when proxying RPCs on proxyd - -## 3.4.1 - -### Patch Changes - -- 415164e1: Force proxyd build - -## 3.4.0 - -### Minor Changes - -- 4b56ed84: Various proxyd fixes - -## 3.3.0 - -### Minor Changes - -- 7b7ffd2e: Allows string RPC ids on proxyd - -## 3.2.0 - -### Minor Changes - -- 73484138: Adds ability to specify env vars in config - -## 3.1.2 - -### Patch Changes - -- 1b79aa62: Release proxyd - -## 3.1.1 - -### Patch Changes - -- b8802054: Trigger release of proxyd -- 34fcb277: Bump proxyd to test release build workflow - -## 3.1.0 - -### Minor Changes - -- da6138fd: Updated metrics, support local rate limiter - -### Patch Changes - -- 6c7f483b: Add support for additional SSL certificates in Docker container - -## 3.0.0 - -### Major Changes - -- abe231bf: Make endpoints match Geth, better logging - -## 2.0.0 - -### Major Changes - -- 6c50098b: Update metrics, support WS -- f827dbda: Brings back the ability to selectively route RPC methods to backend groups - -### Minor Changes - -- 8cc824e5: Updates proxyd to include additional error metrics. -- 9ba4c5e0: Update metrics, support authenticated endpoints -- 78d0f3f0: Put special errors in a dedicated metric, pass along the content-type header - -### Patch Changes - -- 6e6a55b1: Canary release - -## 1.0.2 - -### Patch Changes - -- b9d2fbee: Trigger releases - -## 1.0.1 - -### Patch Changes - -- 893623c9: Trigger patch releases for dockerhub - -## 1.0.0 - -### Major Changes - -- 28aabc41: Initial release of RPC proxy daemon
(deleted)
+0
-32
diff --git OP/proxyd/Dockerfile CELO/proxyd/Dockerfile deleted file mode 100644 index b066e0ecafe7e5233477abc065c27e7143ea45bf..0000000000000000000000000000000000000000 --- OP/proxyd/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM golang:1.21.3-alpine3.18 as builder - -ARG GITCOMMIT=docker -ARG GITDATE=docker -ARG GITVERSION=docker - -RUN apk add make jq git gcc musl-dev linux-headers - -COPY ./proxyd /app - -WORKDIR /app - -RUN make proxyd - -FROM alpine:3.18 - -RUN apk add bind-tools jq curl bash git redis - -COPY ./proxyd/entrypoint.sh /bin/entrypoint.sh - -RUN apk update && \ - apk add ca-certificates && \ - chmod +x /bin/entrypoint.sh - -EXPOSE 8080 - -VOLUME /etc/proxyd - -COPY --from=builder /app/bin/proxyd /bin/proxyd - -ENTRYPOINT ["/bin/entrypoint.sh"] -CMD ["/bin/proxyd", "/etc/proxyd/proxyd.toml"]
diff --git OP/proxyd/Dockerfile.ignore CELO/proxyd/Dockerfile.ignore deleted file mode 100644 index eac1d0bc0b269fe849e171627d1534d6ae22a568..0000000000000000000000000000000000000000 --- OP/proxyd/Dockerfile.ignore +++ /dev/null @@ -1,3 +0,0 @@ -# ignore everything but proxyd, proxyd defines all its dependencies in the go.mod -* -!/proxyd
(deleted)
+0
-25
diff --git OP/proxyd/Makefile CELO/proxyd/Makefile deleted file mode 100644 index d9ffb5742cd652d0082eecc24ad9eaeb2bb56095..0000000000000000000000000000000000000000 --- OP/proxyd/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -proxyd: - go build -v $(LDFLAGS) -o ./bin/proxyd ./cmd/proxyd -.PHONY: proxyd - -fmt: - go mod tidy - gofmt -w . -.PHONY: fmt - -test: - go test -v ./... -.PHONY: test - -lint: - go vet ./... -.PHONY: test - -test-fallback: - go test -v ./... -test.run ^TestFallback$ -.PHONY: test-fallback
diff --git OP/proxyd/README.md CELO/proxyd/README.md index 4a3a84bafc493c61834ae47ac64759fcae800372..f44b815ab2dce959353a80c45e7e919875fad1a4 100644 --- OP/proxyd/README.md +++ CELO/proxyd/README.md @@ -1,146 +1,2 @@ -# rpc-proxy - -This tool implements `proxyd`, an RPC request router and proxy. It does the following things: - -1. Whitelists RPC methods. -2. Routes RPC methods to groups of backend services. -3. Automatically retries failed backend requests. -4. Track backend consensus (`latest`, `safe`, `finalized` blocks), peer count and sync state. -5. Re-write requests and responses to enforce consensus. -6. Load balance requests across backend services. -7. Cache immutable responses from backends. -8. Provides metrics to measure request latency, error rates, and the like. - - -## Usage - -Run `make proxyd` to build the binary. No additional dependencies are necessary. - -To configure `proxyd` for use, you'll need to create a configuration file to define your proxy backends and routing rules. Check out [example.config.toml](./example.config.toml) for how to do this alongside a full list of all options with commentary. - -Once you have a config file, start the daemon via `proxyd <path-to-config>.toml`. - - -## Consensus awareness - -Starting on v4.0.0, `proxyd` is aware of the consensus state of its backends. This helps minimize chain reorgs experienced by clients. - -To enable this behavior, you must set `consensus_aware` value to `true` in the backend group. - -When consensus awareness is enabled, `proxyd` will poll the backends for their states and resolve a consensus group based on: -* the common ancestor `latest` block, i.e. if a backend is experiencing a fork, the fork won't be visible to the clients -* the lowest `safe` block -* the lowest `finalized` block -* peer count -* sync state - -The backend group then acts as a round-robin load balancer distributing traffic equally across healthy backends in the consensus group, increasing the availability of the proxy. - -A backend is considered healthy if it meets the following criteria: -* not banned -* avg 1-min moving window error rate ≤ configurable threshold -* avg 1-min moving window latency ≤ configurable threshold -* peer count ≥ configurable threshold -* `latest` block lag ≤ configurable threshold -* last state update ≤ configurable threshold -* not currently syncing - -When a backend is experiencing inconsistent consensus, high error rates or high latency, -the backend will be banned for a configurable amount of time (default 5 minutes) -and won't receive any traffic during this period. - - -## Tag rewrite - -When consensus awareness is enabled, `proxyd` will enforce the consensus state transparently for all the clients. - -For example, if a client requests the `eth_getBlockByNumber` method with the `latest` tag, -`proxyd` will rewrite the request to use the resolved latest block from the consensus group -and forward it to the backend. - -The following request methods are rewritten: -* `eth_getLogs` -* `eth_newFilter` -* `eth_getBalance` -* `eth_getCode` -* `eth_getTransactionCount` -* `eth_call` -* `eth_getStorageAt` -* `eth_getBlockTransactionCountByNumber` -* `eth_getUncleCountByBlockNumber` -* `eth_getBlockByNumber` -* `eth_getTransactionByBlockNumberAndIndex` -* `eth_getUncleByBlockNumberAndIndex` -* `debug_getRawReceipts` - -And `eth_blockNumber` response is overridden with current block consensus. - - -## Cacheable methods - -Cache use Redis and can be enabled for the following immutable methods: - -* `eth_chainId` -* `net_version` -* `eth_getBlockTransactionCountByHash` -* `eth_getUncleCountByBlockHash` -* `eth_getBlockByHash` -* `eth_getTransactionByBlockHashAndIndex` -* `eth_getUncleByBlockHashAndIndex` -* `debug_getRawReceipts` (block hash only) - -## Meta method `consensus_getReceipts` - -To support backends with different specifications in the same backend group, -proxyd exposes a convenient method to fetch receipts abstracting away -what specific backend will serve the request. - -Each backend specifies their preferred method to fetch receipts with `consensus_receipts_target` config, -which will be translated from `consensus_getReceipts`. - -This method takes a `blockNumberOrHash` (i.e. `tag|qty|hash`) -and returns the receipts for all transactions in the block. - -Request example -```json -{ - "jsonrpc":"2.0", - "id": 1, - "params": ["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"] -} -``` - -It currently supports translation to the following targets: -* `debug_getRawReceipts(blockOrHash)` (default) -* `alchemy_getTransactionReceipts(blockOrHash)` -* `parity_getBlockReceipts(blockOrHash)` -* `eth_getBlockReceipts(blockOrHash)` - -The selected target is returned in the response, in a wrapped result. - -Response example -```json -{ - "jsonrpc": "2.0", - "id": 1, - "result": { - "method": "debug_getRawReceipts", - "result": { - // the actual raw result from backend - } - } -} -``` - -See [op-node receipt fetcher](https://github.com/ethereum-optimism/optimism/blob/186e46a47647a51a658e699e9ff047d39444c2de/op-node/sources/receipts.go#L186-L253). - - -## Metrics - -See `metrics.go` for a list of all available metrics. - -The metrics port is configurable via the `metrics.port` and `metrics.host` keys in the config. - -## Adding Backend SSL Certificates in Docker - -The Docker image runs on Alpine Linux. If you get SSL errors when connecting to a backend within Docker, you may need to add additional certificates to Alpine's certificate store. To do this, bind mount the certificate bundle into a file in `/usr/local/share/ca-certificates`. The `entrypoint.sh` script will then update the store with whatever is in the `ca-certificates` directory prior to starting `proxyd`. +# ⚠️ Important +This project has been moved to [ethereum-optimism/infra](https://github.com/ethereum-optimism/infra)
(deleted)
+0
-1272
diff --git OP/proxyd/backend.go CELO/proxyd/backend.go deleted file mode 100644 index 802b94ab4da7510dea3d43705995f17329fb43df..0000000000000000000000000000000000000000 --- OP/proxyd/backend.go +++ /dev/null @@ -1,1272 +0,0 @@ -package proxyd - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "io" - "math" - "math/rand" - "net/http" - "sort" - "strconv" - "strings" - "sync" - "time" - - sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - "github.com/gorilla/websocket" - "github.com/prometheus/client_golang/prometheus" - "github.com/xaionaro-go/weightedshuffle" - "golang.org/x/sync/semaphore" -) - -const ( - JSONRPCVersion = "2.0" - JSONRPCErrorInternal = -32000 - notFoundRpcError = -32601 -) - -var ( - ErrParseErr = &RPCErr{ - Code: -32700, - Message: "parse error", - HTTPErrorCode: 400, - } - ErrInternal = &RPCErr{ - Code: JSONRPCErrorInternal, - Message: "internal error", - HTTPErrorCode: 500, - } - ErrMethodNotWhitelisted = &RPCErr{ - Code: notFoundRpcError, - Message: "rpc method is not whitelisted", - HTTPErrorCode: 403, - } - ErrBackendOffline = &RPCErr{ - Code: JSONRPCErrorInternal - 10, - Message: "backend offline", - HTTPErrorCode: 503, - } - ErrNoBackends = &RPCErr{ - Code: JSONRPCErrorInternal - 11, - Message: "no backends available for method", - HTTPErrorCode: 503, - } - ErrBackendOverCapacity = &RPCErr{ - Code: JSONRPCErrorInternal - 12, - Message: "backend is over capacity", - HTTPErrorCode: 429, - } - ErrBackendBadResponse = &RPCErr{ - Code: JSONRPCErrorInternal - 13, - Message: "backend returned an invalid response", - HTTPErrorCode: 500, - } - ErrTooManyBatchRequests = &RPCErr{ - Code: JSONRPCErrorInternal - 14, - Message: "too many RPC calls in batch request", - } - ErrGatewayTimeout = &RPCErr{ - Code: JSONRPCErrorInternal - 15, - Message: "gateway timeout", - HTTPErrorCode: 504, - } - ErrOverRateLimit = &RPCErr{ - Code: JSONRPCErrorInternal - 16, - Message: "over rate limit", - HTTPErrorCode: 429, - } - ErrOverSenderRateLimit = &RPCErr{ - Code: JSONRPCErrorInternal - 17, - Message: "sender is over rate limit", - HTTPErrorCode: 429, - } - ErrNotHealthy = &RPCErr{ - Code: JSONRPCErrorInternal - 18, - Message: "backend is currently not healthy to serve traffic", - HTTPErrorCode: 503, - } - ErrBlockOutOfRange = &RPCErr{ - Code: JSONRPCErrorInternal - 19, - Message: "block is out of range", - HTTPErrorCode: 400, - } - - ErrRequestBodyTooLarge = &RPCErr{ - Code: JSONRPCErrorInternal - 21, - Message: "request body too large", - HTTPErrorCode: 413, - } - - ErrBackendResponseTooLarge = &RPCErr{ - Code: JSONRPCErrorInternal - 20, - Message: "backend response too large", - HTTPErrorCode: 500, - } - - ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") - - ErrConsensusGetReceiptsCantBeBatched = errors.New("consensus_getReceipts cannot be batched") - ErrConsensusGetReceiptsInvalidTarget = errors.New("unsupported consensus_receipts_target") -) - -func ErrInvalidRequest(msg string) *RPCErr { - return &RPCErr{ - Code: -32600, - Message: msg, - HTTPErrorCode: 400, - } -} - -func ErrInvalidParams(msg string) *RPCErr { - return &RPCErr{ - Code: -32602, - Message: msg, - HTTPErrorCode: 400, - } -} - -type Backend struct { - Name string - rpcURL string - receiptsTarget string - wsURL string - authUsername string - authPassword string - headers map[string]string - client *LimitedHTTPClient - dialer *websocket.Dialer - maxRetries int - maxResponseSize int64 - maxRPS int - maxWSConns int - outOfServiceInterval time.Duration - stripTrailingXFF bool - proxydIP string - - skipPeerCountCheck bool - forcedCandidate bool - - maxDegradedLatencyThreshold time.Duration - maxLatencyThreshold time.Duration - maxErrorRateThreshold float64 - - latencySlidingWindow *sw.AvgSlidingWindow - networkRequestsSlidingWindow *sw.AvgSlidingWindow - networkErrorsSlidingWindow *sw.AvgSlidingWindow - - weight int -} - -type BackendOpt func(b *Backend) - -func WithBasicAuth(username, password string) BackendOpt { - return func(b *Backend) { - b.authUsername = username - b.authPassword = password - } -} - -func WithHeaders(headers map[string]string) BackendOpt { - return func(b *Backend) { - b.headers = headers - } -} - -func WithTimeout(timeout time.Duration) BackendOpt { - return func(b *Backend) { - b.client.Timeout = timeout - } -} - -func WithMaxRetries(retries int) BackendOpt { - return func(b *Backend) { - b.maxRetries = retries - } -} - -func WithMaxResponseSize(size int64) BackendOpt { - return func(b *Backend) { - b.maxResponseSize = size - } -} - -func WithOutOfServiceDuration(interval time.Duration) BackendOpt { - return func(b *Backend) { - b.outOfServiceInterval = interval - } -} - -func WithMaxRPS(maxRPS int) BackendOpt { - return func(b *Backend) { - b.maxRPS = maxRPS - } -} - -func WithMaxWSConns(maxConns int) BackendOpt { - return func(b *Backend) { - b.maxWSConns = maxConns - } -} - -func WithTLSConfig(tlsConfig *tls.Config) BackendOpt { - return func(b *Backend) { - if b.client.Transport == nil { - b.client.Transport = &http.Transport{} - } - b.client.Transport.(*http.Transport).TLSClientConfig = tlsConfig - } -} - -func WithStrippedTrailingXFF() BackendOpt { - return func(b *Backend) { - b.stripTrailingXFF = true - } -} - -func WithProxydIP(ip string) BackendOpt { - return func(b *Backend) { - b.proxydIP = ip - } -} - -func WithConsensusSkipPeerCountCheck(skipPeerCountCheck bool) BackendOpt { - return func(b *Backend) { - b.skipPeerCountCheck = skipPeerCountCheck - } -} - -func WithConsensusForcedCandidate(forcedCandidate bool) BackendOpt { - return func(b *Backend) { - b.forcedCandidate = forcedCandidate - } -} - -func WithWeight(weight int) BackendOpt { - return func(b *Backend) { - b.weight = weight - } -} - -func WithMaxDegradedLatencyThreshold(maxDegradedLatencyThreshold time.Duration) BackendOpt { - return func(b *Backend) { - b.maxDegradedLatencyThreshold = maxDegradedLatencyThreshold - } -} - -func WithMaxLatencyThreshold(maxLatencyThreshold time.Duration) BackendOpt { - return func(b *Backend) { - b.maxLatencyThreshold = maxLatencyThreshold - } -} - -func WithMaxErrorRateThreshold(maxErrorRateThreshold float64) BackendOpt { - return func(b *Backend) { - b.maxErrorRateThreshold = maxErrorRateThreshold - } -} - -func WithConsensusReceiptTarget(receiptsTarget string) BackendOpt { - return func(b *Backend) { - b.receiptsTarget = receiptsTarget - } -} - -type indexedReqRes struct { - index int - req *RPCReq - res *RPCRes -} - -const proxydHealthzMethod = "proxyd_healthz" - -const ConsensusGetReceiptsMethod = "consensus_getReceipts" - -const ReceiptsTargetDebugGetRawReceipts = "debug_getRawReceipts" -const ReceiptsTargetAlchemyGetTransactionReceipts = "alchemy_getTransactionReceipts" -const ReceiptsTargetParityGetTransactionReceipts = "parity_getBlockReceipts" -const ReceiptsTargetEthGetTransactionReceipts = "eth_getBlockReceipts" - -type ConsensusGetReceiptsResult struct { - Method string `json:"method"` - Result interface{} `json:"result"` -} - -// BlockHashOrNumberParameter is a non-conventional wrapper used by alchemy_getTransactionReceipts -type BlockHashOrNumberParameter struct { - BlockHash *common.Hash `json:"blockHash"` - BlockNumber *rpc.BlockNumber `json:"blockNumber"` -} - -func NewBackend( - name string, - rpcURL string, - wsURL string, - rpcSemaphore *semaphore.Weighted, - opts ...BackendOpt, -) *Backend { - backend := &Backend{ - Name: name, - rpcURL: rpcURL, - wsURL: wsURL, - maxResponseSize: math.MaxInt64, - client: &LimitedHTTPClient{ - Client: http.Client{Timeout: 5 * time.Second}, - sem: rpcSemaphore, - backendName: name, - }, - dialer: &websocket.Dialer{}, - - maxLatencyThreshold: 10 * time.Second, - maxDegradedLatencyThreshold: 5 * time.Second, - maxErrorRateThreshold: 0.5, - - latencySlidingWindow: sw.NewSlidingWindow(), - networkRequestsSlidingWindow: sw.NewSlidingWindow(), - networkErrorsSlidingWindow: sw.NewSlidingWindow(), - } - - backend.Override(opts...) - - if !backend.stripTrailingXFF && backend.proxydIP == "" { - log.Warn("proxied requests' XFF header will not contain the proxyd ip address") - } - - return backend -} - -func (b *Backend) Override(opts ...BackendOpt) { - for _, opt := range opts { - opt(b) - } -} - -func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { - var lastError error - // <= to account for the first attempt not technically being - // a retry - for i := 0; i <= b.maxRetries; i++ { - RecordBatchRPCForward(ctx, b.Name, reqs, RPCRequestSourceHTTP) - metricLabelMethod := reqs[0].Method - if isBatch { - metricLabelMethod = "<batch>" - } - timer := prometheus.NewTimer( - rpcBackendRequestDurationSumm.WithLabelValues( - b.Name, - metricLabelMethod, - strconv.FormatBool(isBatch), - ), - ) - - res, err := b.doForward(ctx, reqs, isBatch) - switch err { - case nil: // do nothing - case ErrBackendResponseTooLarge: - log.Warn( - "backend response too large", - "name", b.Name, - "req_id", GetReqID(ctx), - "max", b.maxResponseSize, - ) - RecordBatchRPCError(ctx, b.Name, reqs, err) - case ErrConsensusGetReceiptsCantBeBatched: - log.Warn( - "Received unsupported batch request for consensus_getReceipts", - "name", b.Name, - "req_id", GetReqID(ctx), - "err", err, - ) - case ErrConsensusGetReceiptsInvalidTarget: - log.Error( - "Unsupported consensus_receipts_target for consensus_getReceipts", - "name", b.Name, - "req_id", GetReqID(ctx), - "err", err, - ) - // ErrBackendUnexpectedJSONRPC occurs because infura responds with a single JSON-RPC object - // to a batch request whenever any Request Object in the batch would induce a partial error. - // We don't label the backend offline in this case. But the error is still returned to - // callers so failover can occur if needed. - case ErrBackendUnexpectedJSONRPC: - log.Debug( - "Received unexpected JSON-RPC response", - "name", b.Name, - "req_id", GetReqID(ctx), - "err", err, - ) - default: - lastError = err - log.Warn( - "backend request failed, trying again", - "name", b.Name, - "req_id", GetReqID(ctx), - "err", err, - ) - timer.ObserveDuration() - RecordBatchRPCError(ctx, b.Name, reqs, err) - sleepContext(ctx, calcBackoff(i)) - continue - } - timer.ObserveDuration() - - MaybeRecordErrorsInRPCRes(ctx, b.Name, reqs, res) - return res, err - } - - return nil, wrapErr(lastError, "permanent error forwarding request") -} - -func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { - backendConn, _, err := b.dialer.Dial(b.wsURL, nil) // nolint:bodyclose - if err != nil { - return nil, wrapErr(err, "error dialing backend") - } - - activeBackendWsConnsGauge.WithLabelValues(b.Name).Inc() - return NewWSProxier(b, clientConn, backendConn, methodWhitelist), nil -} - -// ForwardRPC makes a call directly to a backend and populate the response into `res` -func (b *Backend) ForwardRPC(ctx context.Context, res *RPCRes, id string, method string, params ...any) error { - jsonParams, err := json.Marshal(params) - if err != nil { - return err - } - - rpcReq := RPCReq{ - JSONRPC: JSONRPCVersion, - Method: method, - Params: jsonParams, - ID: []byte(id), - } - - slicedRes, err := b.doForward(ctx, []*RPCReq{&rpcReq}, false) - if err != nil { - return err - } - - if len(slicedRes) != 1 { - return fmt.Errorf("unexpected response len for non-batched request (len != 1)") - } - if slicedRes[0].IsError() { - return fmt.Errorf(slicedRes[0].Error.Error()) - } - - *res = *(slicedRes[0]) - return nil -} - -func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { - // we are concerned about network error rates, so we record 1 request independently of how many are in the batch - b.networkRequestsSlidingWindow.Incr() - - translatedReqs := make(map[string]*RPCReq, len(rpcReqs)) - // translate consensus_getReceipts to receipts target - // right now we only support non-batched - if isBatch { - for _, rpcReq := range rpcReqs { - if rpcReq.Method == ConsensusGetReceiptsMethod { - return nil, ErrConsensusGetReceiptsCantBeBatched - } - } - } else { - for _, rpcReq := range rpcReqs { - if rpcReq.Method == ConsensusGetReceiptsMethod { - translatedReqs[string(rpcReq.ID)] = rpcReq - rpcReq.Method = b.receiptsTarget - var reqParams []rpc.BlockNumberOrHash - err := json.Unmarshal(rpcReq.Params, &reqParams) - if err != nil { - return nil, ErrInvalidRequest("invalid request") - } - - var translatedParams []byte - switch rpcReq.Method { - case ReceiptsTargetDebugGetRawReceipts, - ReceiptsTargetEthGetTransactionReceipts, - ReceiptsTargetParityGetTransactionReceipts: - // conventional methods use an array of strings having either block number or block hash - // i.e. ["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"] - params := make([]string, 1) - if reqParams[0].BlockNumber != nil { - params[0] = reqParams[0].BlockNumber.String() - } else { - params[0] = reqParams[0].BlockHash.Hex() - } - translatedParams = mustMarshalJSON(params) - case ReceiptsTargetAlchemyGetTransactionReceipts: - // alchemy uses an array of object with either block number or block hash - // i.e. [{ blockHash: "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b" }] - params := make([]BlockHashOrNumberParameter, 1) - if reqParams[0].BlockNumber != nil { - params[0].BlockNumber = reqParams[0].BlockNumber - } else { - params[0].BlockHash = reqParams[0].BlockHash - } - translatedParams = mustMarshalJSON(params) - default: - return nil, ErrConsensusGetReceiptsInvalidTarget - } - - rpcReq.Params = translatedParams - } - } - } - - isSingleElementBatch := len(rpcReqs) == 1 - - // Single element batches are unwrapped before being sent - // since Alchemy handles single requests better than batches. - var body []byte - if isSingleElementBatch { - body = mustMarshalJSON(rpcReqs[0]) - } else { - body = mustMarshalJSON(rpcReqs) - } - - httpReq, err := http.NewRequestWithContext(ctx, "POST", b.rpcURL, bytes.NewReader(body)) - if err != nil { - b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - return nil, wrapErr(err, "error creating backend request") - } - - if b.authPassword != "" { - httpReq.SetBasicAuth(b.authUsername, b.authPassword) - } - - xForwardedFor := GetXForwardedFor(ctx) - if b.stripTrailingXFF { - xForwardedFor = stripXFF(xForwardedFor) - } else if b.proxydIP != "" { - xForwardedFor = fmt.Sprintf("%s, %s", xForwardedFor, b.proxydIP) - } - - httpReq.Header.Set("content-type", "application/json") - httpReq.Header.Set("X-Forwarded-For", xForwardedFor) - - for name, value := range b.headers { - httpReq.Header.Set(name, value) - } - - start := time.Now() - httpRes, err := b.client.DoLimited(httpReq) - if err != nil { - b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - return nil, wrapErr(err, "error in backend request") - } - - metricLabelMethod := rpcReqs[0].Method - if isBatch { - metricLabelMethod = "<batch>" - } - rpcBackendHTTPResponseCodesTotal.WithLabelValues( - GetAuthCtx(ctx), - b.Name, - metricLabelMethod, - strconv.Itoa(httpRes.StatusCode), - strconv.FormatBool(isBatch), - ).Inc() - - // Alchemy returns a 400 on bad JSONs, so handle that case - if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 { - b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - return nil, fmt.Errorf("response code %d", httpRes.StatusCode) - } - - defer httpRes.Body.Close() - resB, err := io.ReadAll(LimitReader(httpRes.Body, b.maxResponseSize)) - if errors.Is(err, ErrLimitReaderOverLimit) { - return nil, ErrBackendResponseTooLarge - } - if err != nil { - b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - return nil, wrapErr(err, "error reading response body") - } - - var rpcRes []*RPCRes - if isSingleElementBatch { - var singleRes RPCRes - if err := json.Unmarshal(resB, &singleRes); err != nil { - return nil, ErrBackendBadResponse - } - rpcRes = []*RPCRes{ - &singleRes, - } - } else { - if err := json.Unmarshal(resB, &rpcRes); err != nil { - // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method - if responseIsNotBatched(resB) { - b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - return nil, ErrBackendUnexpectedJSONRPC - } - b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - return nil, ErrBackendBadResponse - } - } - - if len(rpcReqs) != len(rpcRes) { - b.networkErrorsSlidingWindow.Incr() - RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - return nil, ErrBackendUnexpectedJSONRPC - } - - // capture the HTTP status code in the response. this will only - // ever be 400 given the status check on line 318 above. - if httpRes.StatusCode != 200 { - for _, res := range rpcRes { - res.Error.HTTPErrorCode = httpRes.StatusCode - } - } - duration := time.Since(start) - b.latencySlidingWindow.Add(float64(duration)) - RecordBackendNetworkLatencyAverageSlidingWindow(b, time.Duration(b.latencySlidingWindow.Avg())) - RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) - - // enrich the response with the actual request method - for _, res := range rpcRes { - translatedReq, exist := translatedReqs[string(res.ID)] - if exist { - res.Result = ConsensusGetReceiptsResult{ - Method: translatedReq.Method, - Result: res.Result, - } - } - } - - sortBatchRPCResponse(rpcReqs, rpcRes) - - return rpcRes, nil -} - -// IsHealthy checks if the backend is able to serve traffic, based on dynamic parameters -func (b *Backend) IsHealthy() bool { - errorRate := b.ErrorRate() - avgLatency := time.Duration(b.latencySlidingWindow.Avg()) - if errorRate >= b.maxErrorRateThreshold { - return false - } - if avgLatency >= b.maxLatencyThreshold { - return false - } - return true -} - -// ErrorRate returns the instant error rate of the backend -func (b *Backend) ErrorRate() (errorRate float64) { - // we only really start counting the error rate after a minimum of 10 requests - // this is to avoid false positives when the backend is just starting up - if b.networkRequestsSlidingWindow.Sum() >= 10 { - errorRate = b.networkErrorsSlidingWindow.Sum() / b.networkRequestsSlidingWindow.Sum() - } - return errorRate -} - -// IsDegraded checks if the backend is serving traffic in a degraded state (i.e. used as a last resource) -func (b *Backend) IsDegraded() bool { - avgLatency := time.Duration(b.latencySlidingWindow.Avg()) - return avgLatency >= b.maxDegradedLatencyThreshold -} - -func responseIsNotBatched(b []byte) bool { - var r RPCRes - return json.Unmarshal(b, &r) == nil -} - -// sortBatchRPCResponse sorts the RPCRes slice according to the position of its corresponding ID in the RPCReq slice -func sortBatchRPCResponse(req []*RPCReq, res []*RPCRes) { - pos := make(map[string]int, len(req)) - for i, r := range req { - key := string(r.ID) - if _, ok := pos[key]; ok { - panic("bug! detected requests with duplicate IDs") - } - pos[key] = i - } - - sort.Slice(res, func(i, j int) bool { - l := res[i].ID - r := res[j].ID - return pos[string(l)] < pos[string(r)] - }) -} - -type BackendGroup struct { - Name string - Backends []*Backend - WeightedRouting bool - Consensus *ConsensusPoller - FallbackBackends map[string]bool -} - -func (bg *BackendGroup) Fallbacks() []*Backend { - fallbacks := []*Backend{} - for _, a := range bg.Backends { - if fallback, ok := bg.FallbackBackends[a.Name]; ok && fallback { - fallbacks = append(fallbacks, a) - } - } - return fallbacks -} - -func (bg *BackendGroup) Primaries() []*Backend { - primaries := []*Backend{} - for _, a := range bg.Backends { - fallback, ok := bg.FallbackBackends[a.Name] - if ok && !fallback { - primaries = append(primaries, a) - } - } - return primaries -} - -// NOTE: BackendGroup Forward contains the log for balancing with consensus aware -func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, string, error) { - if len(rpcReqs) == 0 { - return nil, "", nil - } - - backends := bg.orderedBackendsForRequest() - - overriddenResponses := make([]*indexedReqRes, 0) - rewrittenReqs := make([]*RPCReq, 0, len(rpcReqs)) - - if bg.Consensus != nil { - // When `consensus_aware` is set to `true`, the backend group acts as a load balancer - // serving traffic from any backend that agrees in the consensus group - - // We also rewrite block tags to enforce compliance with consensus - rctx := RewriteContext{ - latest: bg.Consensus.GetLatestBlockNumber(), - safe: bg.Consensus.GetSafeBlockNumber(), - finalized: bg.Consensus.GetFinalizedBlockNumber(), - maxBlockRange: bg.Consensus.maxBlockRange, - } - - for i, req := range rpcReqs { - res := RPCRes{JSONRPC: JSONRPCVersion, ID: req.ID} - result, err := RewriteTags(rctx, req, &res) - switch result { - case RewriteOverrideError: - overriddenResponses = append(overriddenResponses, &indexedReqRes{ - index: i, - req: req, - res: &res, - }) - if errors.Is(err, ErrRewriteBlockOutOfRange) { - res.Error = ErrBlockOutOfRange - } else if errors.Is(err, ErrRewriteRangeTooLarge) { - res.Error = ErrInvalidParams( - fmt.Sprintf("block range greater than %d max", rctx.maxBlockRange), - ) - } else { - res.Error = ErrParseErr - } - case RewriteOverrideResponse: - overriddenResponses = append(overriddenResponses, &indexedReqRes{ - index: i, - req: req, - res: &res, - }) - case RewriteOverrideRequest, RewriteNone: - rewrittenReqs = append(rewrittenReqs, req) - } - } - rpcReqs = rewrittenReqs - } - - rpcRequestsTotal.Inc() - - for _, back := range backends { - res := make([]*RPCRes, 0) - var err error - - servedBy := fmt.Sprintf("%s/%s", bg.Name, back.Name) - - if len(rpcReqs) > 0 { - res, err = back.Forward(ctx, rpcReqs, isBatch) - if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || - errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) || - errors.Is(err, ErrMethodNotWhitelisted) { - return nil, "", err - } - if errors.Is(err, ErrBackendResponseTooLarge) { - return nil, servedBy, err - } - if errors.Is(err, ErrBackendOffline) { - log.Warn( - "skipping offline backend", - "name", back.Name, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - continue - } - if errors.Is(err, ErrBackendOverCapacity) { - log.Warn( - "skipping over-capacity backend", - "name", back.Name, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - continue - } - if err != nil { - log.Error( - "error forwarding request to backend", - "name", back.Name, - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - "err", err, - ) - continue - } - } - - // re-apply overridden responses - for _, ov := range overriddenResponses { - if len(res) > 0 { - // insert ov.res at position ov.index - res = append(res[:ov.index], append([]*RPCRes{ov.res}, res[ov.index:]...)...) - } else { - res = append(res, ov.res) - } - } - - return res, servedBy, nil - } - - RecordUnserviceableRequest(ctx, RPCRequestSourceHTTP) - return nil, "", ErrNoBackends -} - -func (bg *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { - for _, back := range bg.Backends { - proxier, err := back.ProxyWS(clientConn, methodWhitelist) - if errors.Is(err, ErrBackendOffline) { - log.Warn( - "skipping offline backend", - "name", back.Name, - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - ) - continue - } - if errors.Is(err, ErrBackendOverCapacity) { - log.Warn( - "skipping over-capacity backend", - "name", back.Name, - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - ) - continue - } - if err != nil { - log.Warn( - "error dialing ws backend", - "name", back.Name, - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - "err", err, - ) - continue - } - return proxier, nil - } - - return nil, ErrNoBackends -} - -func weightedShuffle(backends []*Backend) { - weight := func(i int) float64 { - return float64(backends[i].weight) - } - - weightedshuffle.ShuffleInplace(backends, weight, nil) -} - -func (bg *BackendGroup) orderedBackendsForRequest() []*Backend { - if bg.Consensus != nil { - return bg.loadBalancedConsensusGroup() - } else if bg.WeightedRouting { - result := make([]*Backend, len(bg.Backends)) - copy(result, bg.Backends) - weightedShuffle(result) - return result - } else { - return bg.Backends - } -} - -func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { - cg := bg.Consensus.GetConsensusGroup() - - backendsHealthy := make([]*Backend, 0, len(cg)) - backendsDegraded := make([]*Backend, 0, len(cg)) - // separate into healthy, degraded and unhealthy backends - for _, be := range cg { - // unhealthy are filtered out and not attempted - if !be.IsHealthy() { - continue - } - if be.IsDegraded() { - backendsDegraded = append(backendsDegraded, be) - continue - } - backendsHealthy = append(backendsHealthy, be) - } - - // shuffle both slices - r := rand.New(rand.NewSource(time.Now().UnixNano())) - r.Shuffle(len(backendsHealthy), func(i, j int) { - backendsHealthy[i], backendsHealthy[j] = backendsHealthy[j], backendsHealthy[i] - }) - r.Shuffle(len(backendsDegraded), func(i, j int) { - backendsDegraded[i], backendsDegraded[j] = backendsDegraded[j], backendsDegraded[i] - }) - - if bg.WeightedRouting { - weightedShuffle(backendsHealthy) - } - - // healthy are put into a priority position - // degraded backends are used as fallback - backendsHealthy = append(backendsHealthy, backendsDegraded...) - - return backendsHealthy -} - -func (bg *BackendGroup) Shutdown() { - if bg.Consensus != nil { - bg.Consensus.Shutdown() - } -} - -func calcBackoff(i int) time.Duration { - jitter := float64(rand.Int63n(250)) - ms := math.Min(math.Pow(2, float64(i))*1000+jitter, 3000) - return time.Duration(ms) * time.Millisecond -} - -type WSProxier struct { - backend *Backend - clientConn *websocket.Conn - clientConnMu sync.Mutex - backendConn *websocket.Conn - backendConnMu sync.Mutex - methodWhitelist *StringSet - readTimeout time.Duration - writeTimeout time.Duration -} - -func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, methodWhitelist *StringSet) *WSProxier { - return &WSProxier{ - backend: backend, - clientConn: clientConn, - backendConn: backendConn, - methodWhitelist: methodWhitelist, - readTimeout: defaultWSReadTimeout, - writeTimeout: defaultWSWriteTimeout, - } -} - -func (w *WSProxier) Proxy(ctx context.Context) error { - errC := make(chan error, 2) - go w.clientPump(ctx, errC) - go w.backendPump(ctx, errC) - err := <-errC - w.close() - return err -} - -func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { - for { - // Block until we get a message. - msgType, msg, err := w.clientConn.ReadMessage() - if err != nil { - if err := w.writeBackendConn(websocket.CloseMessage, formatWSError(err)); err != nil { - log.Error("error writing backendConn message", "err", err) - errC <- err - return - } - } - - RecordWSMessage(ctx, w.backend.Name, SourceClient) - - // Route control messages to the backend. These don't - // count towards the total RPC requests count. - if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { - err := w.writeBackendConn(msgType, msg) - if err != nil { - errC <- err - return - } - continue - } - - rpcRequestsTotal.Inc() - - // Don't bother sending invalid requests to the backend, - // just handle them here. - req, err := w.prepareClientMsg(msg) - if err != nil { - var id json.RawMessage - method := MethodUnknown - if req != nil { - id = req.ID - method = req.Method - } - log.Info( - "error preparing client message", - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - "err", err, - ) - msg = mustMarshalJSON(NewRPCErrorRes(id, err)) - RecordRPCError(ctx, BackendProxyd, method, err) - - // Send error response to client - err = w.writeClientConn(msgType, msg) - if err != nil { - errC <- err - return - } - continue - } - - // Send eth_accounts requests directly to the client - if req.Method == "eth_accounts" { - msg = mustMarshalJSON(NewRPCRes(req.ID, emptyArrayResponse)) - RecordRPCForward(ctx, BackendProxyd, "eth_accounts", RPCRequestSourceWS) - err = w.writeClientConn(msgType, msg) - if err != nil { - errC <- err - return - } - continue - } - - RecordRPCForward(ctx, w.backend.Name, req.Method, RPCRequestSourceWS) - log.Info( - "forwarded WS message to backend", - "method", req.Method, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - - err = w.writeBackendConn(msgType, msg) - if err != nil { - errC <- err - return - } - } -} - -func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { - for { - // Block until we get a message. - msgType, msg, err := w.backendConn.ReadMessage() - if err != nil { - if err := w.writeClientConn(websocket.CloseMessage, formatWSError(err)); err != nil { - log.Error("error writing clientConn message", "err", err) - errC <- err - return - } - } - - RecordWSMessage(ctx, w.backend.Name, SourceBackend) - - // Route control messages directly to the client. - if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { - err := w.writeClientConn(msgType, msg) - if err != nil { - errC <- err - return - } - continue - } - - res, err := w.parseBackendMsg(msg) - if err != nil { - var id json.RawMessage - if res != nil { - id = res.ID - } - msg = mustMarshalJSON(NewRPCErrorRes(id, err)) - log.Info("backend responded with error", "err", err) - } else { - if res.IsError() { - log.Info( - "backend responded with RPC error", - "code", res.Error.Code, - "msg", res.Error.Message, - "source", "ws", - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - RecordRPCError(ctx, w.backend.Name, MethodUnknown, res.Error) - } else { - log.Info( - "forwarded WS message to client", - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - } - } - - err = w.writeClientConn(msgType, msg) - if err != nil { - errC <- err - return - } - } -} - -func (w *WSProxier) close() { - w.clientConn.Close() - w.backendConn.Close() - activeBackendWsConnsGauge.WithLabelValues(w.backend.Name).Dec() -} - -func (w *WSProxier) prepareClientMsg(msg []byte) (*RPCReq, error) { - req, err := ParseRPCReq(msg) - if err != nil { - return nil, err - } - - if !w.methodWhitelist.Has(req.Method) { - return req, ErrMethodNotWhitelisted - } - - return req, nil -} - -func (w *WSProxier) parseBackendMsg(msg []byte) (*RPCRes, error) { - res, err := ParseRPCRes(bytes.NewReader(msg)) - if err != nil { - log.Warn("error parsing RPC response", "source", "ws", "err", err) - return res, ErrBackendBadResponse - } - return res, nil -} - -func (w *WSProxier) writeClientConn(msgType int, msg []byte) error { - w.clientConnMu.Lock() - defer w.clientConnMu.Unlock() - if err := w.clientConn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil { - log.Error("ws client write timeout", "err", err) - return err - } - err := w.clientConn.WriteMessage(msgType, msg) - return err -} - -func (w *WSProxier) writeBackendConn(msgType int, msg []byte) error { - w.backendConnMu.Lock() - defer w.backendConnMu.Unlock() - if err := w.backendConn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil { - log.Error("ws backend write timeout", "err", err) - return err - } - err := w.backendConn.WriteMessage(msgType, msg) - return err -} - -func mustMarshalJSON(in interface{}) []byte { - out, err := json.Marshal(in) - if err != nil { - panic(err) - } - return out -} - -func formatWSError(err error) []byte { - m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err)) - if e, ok := err.(*websocket.CloseError); ok { - if e.Code != websocket.CloseNoStatusReceived { - m = websocket.FormatCloseMessage(e.Code, e.Text) - } - } - return m -} - -func sleepContext(ctx context.Context, duration time.Duration) { - select { - case <-ctx.Done(): - case <-time.After(duration): - } -} - -type LimitedHTTPClient struct { - http.Client - sem *semaphore.Weighted - backendName string -} - -func (c *LimitedHTTPClient) DoLimited(req *http.Request) (*http.Response, error) { - if err := c.sem.Acquire(req.Context(), 1); err != nil { - tooManyRequestErrorsTotal.WithLabelValues(c.backendName).Inc() - return nil, wrapErr(err, "too many requests") - } - defer c.sem.Release(1) - return c.Do(req) -} - -func RecordBatchRPCError(ctx context.Context, backendName string, reqs []*RPCReq, err error) { - for _, req := range reqs { - RecordRPCError(ctx, backendName, req.Method, err) - } -} - -func MaybeRecordErrorsInRPCRes(ctx context.Context, backendName string, reqs []*RPCReq, resBatch []*RPCRes) { - log.Info("forwarded RPC request", - "backend", backendName, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - "batch_size", len(reqs), - ) - - var lastError *RPCErr - for i, res := range resBatch { - if res.IsError() { - lastError = res.Error - RecordRPCError(ctx, backendName, reqs[i].Method, res.Error) - } - } - - if lastError != nil { - log.Info( - "backend responded with RPC error", - "backend", backendName, - "last_error_code", lastError.Code, - "last_error_msg", lastError.Message, - "req_id", GetReqID(ctx), - "source", "rpc", - "auth", GetAuthCtx(ctx), - ) - } -} - -func RecordBatchRPCForward(ctx context.Context, backendName string, reqs []*RPCReq, source string) { - for _, req := range reqs { - RecordRPCForward(ctx, backendName, req.Method, source) - } -} - -func stripXFF(xff string) string { - ipList := strings.Split(xff, ",") - return strings.TrimSpace(ipList[0]) -}
diff --git OP/proxyd/backend_test.go CELO/proxyd/backend_test.go deleted file mode 100644 index 7be23bfed7bc563174d4b7084bdb70a6b4d1c7db..0000000000000000000000000000000000000000 --- OP/proxyd/backend_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package proxyd - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestStripXFF(t *testing.T) { - tests := []struct { - in, out string - }{ - {"1.2.3, 4.5.6, 7.8.9", "1.2.3"}, - {"1.2.3,4.5.6", "1.2.3"}, - {" 1.2.3 , 4.5.6 ", "1.2.3"}, - } - - for _, test := range tests { - actual := stripXFF(test.in) - assert.Equal(t, test.out, actual) - } -}
(deleted)
+0
-192
diff --git OP/proxyd/cache.go CELO/proxyd/cache.go deleted file mode 100644 index 5add4f23627ea7dea881c3a2a766450c78ff2d8e..0000000000000000000000000000000000000000 --- OP/proxyd/cache.go +++ /dev/null @@ -1,192 +0,0 @@ -package proxyd - -import ( - "context" - "encoding/json" - "strings" - "time" - - "github.com/ethereum/go-ethereum/rpc" - "github.com/redis/go-redis/v9" - - "github.com/golang/snappy" - lru "github.com/hashicorp/golang-lru" -) - -type Cache interface { - Get(ctx context.Context, key string) (string, error) - Put(ctx context.Context, key string, value string) error -} - -const ( - // assuming an average RPCRes size of 3 KB - memoryCacheLimit = 4096 -) - -type cache struct { - lru *lru.Cache -} - -func newMemoryCache() *cache { - rep, _ := lru.New(memoryCacheLimit) - return &cache{rep} -} - -func (c *cache) Get(ctx context.Context, key string) (string, error) { - if val, ok := c.lru.Get(key); ok { - return val.(string), nil - } - return "", nil -} - -func (c *cache) Put(ctx context.Context, key string, value string) error { - c.lru.Add(key, value) - return nil -} - -type redisCache struct { - rdb *redis.Client - prefix string - ttl time.Duration -} - -func newRedisCache(rdb *redis.Client, prefix string, ttl time.Duration) *redisCache { - return &redisCache{rdb, prefix, ttl} -} - -func (c *redisCache) namespaced(key string) string { - if c.prefix == "" { - return key - } - return strings.Join([]string{c.prefix, key}, ":") -} - -func (c *redisCache) Get(ctx context.Context, key string) (string, error) { - start := time.Now() - val, err := c.rdb.Get(ctx, c.namespaced(key)).Result() - redisCacheDurationSumm.WithLabelValues("GET").Observe(float64(time.Since(start).Milliseconds())) - - if err == redis.Nil { - return "", nil - } else if err != nil { - RecordRedisError("CacheGet") - return "", err - } - return val, nil -} - -func (c *redisCache) Put(ctx context.Context, key string, value string) error { - start := time.Now() - err := c.rdb.SetEx(ctx, c.namespaced(key), value, c.ttl).Err() - redisCacheDurationSumm.WithLabelValues("SETEX").Observe(float64(time.Since(start).Milliseconds())) - - if err != nil { - RecordRedisError("CacheSet") - } - return err -} - -type cacheWithCompression struct { - cache Cache -} - -func newCacheWithCompression(cache Cache) *cacheWithCompression { - return &cacheWithCompression{cache} -} - -func (c *cacheWithCompression) Get(ctx context.Context, key string) (string, error) { - encodedVal, err := c.cache.Get(ctx, key) - if err != nil { - return "", err - } - if encodedVal == "" { - return "", nil - } - val, err := snappy.Decode(nil, []byte(encodedVal)) - if err != nil { - return "", err - } - return string(val), nil -} - -func (c *cacheWithCompression) Put(ctx context.Context, key string, value string) error { - encodedVal := snappy.Encode(nil, []byte(value)) - return c.cache.Put(ctx, key, string(encodedVal)) -} - -type RPCCache interface { - GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) - PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error -} - -type rpcCache struct { - cache Cache - handlers map[string]RPCMethodHandler -} - -func newRPCCache(cache Cache) RPCCache { - staticHandler := &StaticMethodHandler{cache: cache} - debugGetRawReceiptsHandler := &StaticMethodHandler{cache: cache, - filterGet: func(req *RPCReq) bool { - // cache only if the request is for a block hash - - var p []rpc.BlockNumberOrHash - err := json.Unmarshal(req.Params, &p) - if err != nil { - return false - } - if len(p) != 1 { - return false - } - return p[0].BlockHash != nil - }, - filterPut: func(req *RPCReq, res *RPCRes) bool { - // don't cache if response contains 0 receipts - rawReceipts, ok := res.Result.([]interface{}) - if !ok { - return false - } - return len(rawReceipts) > 0 - }, - } - handlers := map[string]RPCMethodHandler{ - "eth_chainId": staticHandler, - "net_version": staticHandler, - "eth_getBlockTransactionCountByHash": staticHandler, - "eth_getUncleCountByBlockHash": staticHandler, - "eth_getBlockByHash": staticHandler, - "eth_getTransactionByBlockHashAndIndex": staticHandler, - "eth_getUncleByBlockHashAndIndex": staticHandler, - "debug_getRawReceipts": debugGetRawReceiptsHandler, - } - return &rpcCache{ - cache: cache, - handlers: handlers, - } -} - -func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) { - handler := c.handlers[req.Method] - if handler == nil { - return nil, nil - } - res, err := handler.GetRPCMethod(ctx, req) - if err != nil { - RecordCacheError(req.Method) - return nil, err - } - if res == nil { - RecordCacheMiss(req.Method) - } else { - RecordCacheHit(req.Method) - } - return res, nil -} - -func (c *rpcCache) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error { - handler := c.handlers[req.Method] - if handler == nil { - return nil - } - return handler.PutRPCMethod(ctx, req, res) -}
(deleted)
+0
-213
diff --git OP/proxyd/cache_test.go CELO/proxyd/cache_test.go deleted file mode 100644 index 1a5d543227ae99f2f705355af16f467cdf2d307d..0000000000000000000000000000000000000000 --- OP/proxyd/cache_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package proxyd - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestRPCCacheImmutableRPCs(t *testing.T) { - ctx := context.Background() - - cache := newRPCCache(newMemoryCache()) - ID := []byte(strconv.Itoa(1)) - - rpcs := []struct { - req *RPCReq - res *RPCRes - name string - }{ - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_chainId", - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: "0xff", - ID: ID, - }, - name: "eth_chainId", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "net_version", - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: "9999", - ID: ID, - }, - name: "net_version", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockTransactionCountByHash", - Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"eth_getBlockTransactionCountByHash":"!"}`, - ID: ID, - }, - name: "eth_getBlockTransactionCountByHash", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getUncleCountByBlockHash", - Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"eth_getUncleCountByBlockHash":"!"}`, - ID: ID, - }, - name: "eth_getUncleCountByBlockHash", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByHash", - Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"eth_getBlockByHash":"!"}`, - ID: ID, - }, - name: "eth_getBlockByHash", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getUncleByBlockHashAndIndex", - Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238", "0x90"}), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"eth_getUncleByBlockHashAndIndex":"!"}`, - ID: ID, - }, - name: "eth_getUncleByBlockHashAndIndex", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "debug_getRawReceipts", - Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: []interface{}{"a"}, - ID: ID, - }, - name: "debug_getRawReceipts", - }, - } - - for _, rpc := range rpcs { - t.Run(rpc.name, func(t *testing.T) { - err := cache.PutRPC(ctx, rpc.req, rpc.res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, rpc.req) - require.NoError(t, err) - require.Equal(t, rpc.res, cachedRes) - }) - } -} - -func TestRPCCacheUnsupportedMethod(t *testing.T) { - ctx := context.Background() - - cache := newRPCCache(newMemoryCache()) - ID := []byte(strconv.Itoa(1)) - - rpcs := []struct { - req *RPCReq - name string - }{ - { - name: "eth_syncing", - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_syncing", - ID: ID, - }, - }, - { - name: "eth_blockNumber", - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_blockNumber", - ID: ID, - }, - }, - { - name: "eth_getBlockByNumber", - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - ID: ID, - }, - }, - { - name: "eth_getBlockRange", - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - ID: ID, - }, - }, - { - name: "eth_gasPrice", - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_gasPrice", - ID: ID, - }, - }, - { - name: "eth_call", - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_call", - ID: ID, - }, - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "debug_getRawReceipts", - Params: mustMarshalJSON([]string{"0x100"}), - ID: ID, - }, - name: "debug_getRawReceipts", - }, - } - - for _, rpc := range rpcs { - t.Run(rpc.name, func(t *testing.T) { - fakeval := mustMarshalJSON([]string{rpc.name}) - err := cache.PutRPC(ctx, rpc.req, &RPCRes{Result: fakeval}) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, rpc.req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) - } - -}
(deleted)
+0
-184
diff --git OP/proxyd/config.go CELO/proxyd/config.go deleted file mode 100644 index 4719a55f85c1d3fe9ca841a92fff2e8b53bfc59a..0000000000000000000000000000000000000000 --- OP/proxyd/config.go +++ /dev/null @@ -1,184 +0,0 @@ -package proxyd - -import ( - "fmt" - "math/big" - "os" - "strings" - "time" -) - -type ServerConfig struct { - RPCHost string `toml:"rpc_host"` - RPCPort int `toml:"rpc_port"` - WSHost string `toml:"ws_host"` - WSPort int `toml:"ws_port"` - MaxBodySizeBytes int64 `toml:"max_body_size_bytes"` - MaxConcurrentRPCs int64 `toml:"max_concurrent_rpcs"` - LogLevel string `toml:"log_level"` - - // TimeoutSeconds specifies the maximum time spent serving an HTTP request. Note that isn't used for websocket connections - TimeoutSeconds int `toml:"timeout_seconds"` - - MaxUpstreamBatchSize int `toml:"max_upstream_batch_size"` - - EnableRequestLog bool `toml:"enable_request_log"` - MaxRequestBodyLogLen int `toml:"max_request_body_log_len"` - EnablePprof bool `toml:"enable_pprof"` - EnableXServedByHeader bool `toml:"enable_served_by_header"` - AllowAllOrigins bool `toml:"allow_all_origins"` -} - -type CacheConfig struct { - Enabled bool `toml:"enabled"` - TTL TOMLDuration `toml:"ttl"` -} - -type RedisConfig struct { - URL string `toml:"url"` - Namespace string `toml:"namespace"` -} - -type MetricsConfig struct { - Enabled bool `toml:"enabled"` - Host string `toml:"host"` - Port int `toml:"port"` -} - -type RateLimitConfig struct { - UseRedis bool `toml:"use_redis"` - BaseRate int `toml:"base_rate"` - BaseInterval TOMLDuration `toml:"base_interval"` - ExemptOrigins []string `toml:"exempt_origins"` - ExemptUserAgents []string `toml:"exempt_user_agents"` - ErrorMessage string `toml:"error_message"` - MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` - IPHeaderOverride string `toml:"ip_header_override"` -} - -type RateLimitMethodOverride struct { - Limit int `toml:"limit"` - Interval TOMLDuration `toml:"interval"` - Global bool `toml:"global"` -} - -type TOMLDuration time.Duration - -func (t *TOMLDuration) UnmarshalText(b []byte) error { - d, err := time.ParseDuration(string(b)) - if err != nil { - return err - } - - *t = TOMLDuration(d) - return nil -} - -type BackendOptions struct { - ResponseTimeoutSeconds int `toml:"response_timeout_seconds"` - MaxResponseSizeBytes int64 `toml:"max_response_size_bytes"` - MaxRetries int `toml:"max_retries"` - OutOfServiceSeconds int `toml:"out_of_service_seconds"` - MaxDegradedLatencyThreshold TOMLDuration `toml:"max_degraded_latency_threshold"` - MaxLatencyThreshold TOMLDuration `toml:"max_latency_threshold"` - MaxErrorRateThreshold float64 `toml:"max_error_rate_threshold"` -} - -type BackendConfig struct { - Username string `toml:"username"` - Password string `toml:"password"` - RPCURL string `toml:"rpc_url"` - WSURL string `toml:"ws_url"` - WSPort int `toml:"ws_port"` - MaxRPS int `toml:"max_rps"` - MaxWSConns int `toml:"max_ws_conns"` - CAFile string `toml:"ca_file"` - ClientCertFile string `toml:"client_cert_file"` - ClientKeyFile string `toml:"client_key_file"` - StripTrailingXFF bool `toml:"strip_trailing_xff"` - Headers map[string]string `toml:"headers"` - - Weight int `toml:"weight"` - - ConsensusSkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` - ConsensusForcedCandidate bool `toml:"consensus_forced_candidate"` - ConsensusReceiptsTarget string `toml:"consensus_receipts_target"` -} - -type BackendsConfig map[string]*BackendConfig - -type BackendGroupConfig struct { - Backends []string `toml:"backends"` - - WeightedRouting bool `toml:"weighted_routing"` - - ConsensusAware bool `toml:"consensus_aware"` - ConsensusAsyncHandler string `toml:"consensus_handler"` - ConsensusPollerInterval TOMLDuration `toml:"consensus_poller_interval"` - - ConsensusBanPeriod TOMLDuration `toml:"consensus_ban_period"` - ConsensusMaxUpdateThreshold TOMLDuration `toml:"consensus_max_update_threshold"` - ConsensusMaxBlockLag uint64 `toml:"consensus_max_block_lag"` - ConsensusMaxBlockRange uint64 `toml:"consensus_max_block_range"` - ConsensusMinPeerCount int `toml:"consensus_min_peer_count"` - - ConsensusHA bool `toml:"consensus_ha"` - ConsensusHAHeartbeatInterval TOMLDuration `toml:"consensus_ha_heartbeat_interval"` - ConsensusHALockPeriod TOMLDuration `toml:"consensus_ha_lock_period"` - ConsensusHARedis RedisConfig `toml:"consensus_ha_redis"` - - Fallbacks []string `toml:"fallbacks"` -} - -type BackendGroupsConfig map[string]*BackendGroupConfig - -type MethodMappingsConfig map[string]string - -type BatchConfig struct { - MaxSize int `toml:"max_size"` - ErrorMessage string `toml:"error_message"` -} - -// SenderRateLimitConfig configures the sender-based rate limiter -// for eth_sendRawTransaction requests. -// To enable pre-eip155 transactions, add '0' to allowed_chain_ids. -type SenderRateLimitConfig struct { - Enabled bool - Interval TOMLDuration - Limit int - AllowedChainIds []*big.Int `toml:"allowed_chain_ids"` -} - -type Config struct { - WSBackendGroup string `toml:"ws_backend_group"` - Server ServerConfig `toml:"server"` - Cache CacheConfig `toml:"cache"` - Redis RedisConfig `toml:"redis"` - Metrics MetricsConfig `toml:"metrics"` - RateLimit RateLimitConfig `toml:"rate_limit"` - BackendOptions BackendOptions `toml:"backend"` - Backends BackendsConfig `toml:"backends"` - BatchConfig BatchConfig `toml:"batch"` - Authentication map[string]string `toml:"authentication"` - BackendGroups BackendGroupsConfig `toml:"backend_groups"` - RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` - WSMethodWhitelist []string `toml:"ws_method_whitelist"` - WhitelistErrorMessage string `toml:"whitelist_error_message"` - SenderRateLimit SenderRateLimitConfig `toml:"sender_rate_limit"` -} - -func ReadFromEnvOrConfig(value string) (string, error) { - if strings.HasPrefix(value, "$") { - envValue := os.Getenv(strings.TrimPrefix(value, "$")) - if envValue == "" { - return "", fmt.Errorf("config env var %s not found", value) - } - return envValue, nil - } - - if strings.HasPrefix(value, "\\") { - return strings.TrimPrefix(value, "\\"), nil - } - - return value, nil -}
diff --git OP/proxyd/consensus_poller.go CELO/proxyd/consensus_poller.go deleted file mode 100644 index 90af41db7067c106c812a92f3510c44b55ebdd80..0000000000000000000000000000000000000000 --- OP/proxyd/consensus_poller.go +++ /dev/null @@ -1,746 +0,0 @@ -package proxyd - -import ( - "context" - "fmt" - "strconv" - "strings" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/ethereum/go-ethereum/log" -) - -const ( - DefaultPollerInterval = 1 * time.Second -) - -type OnConsensusBroken func() - -// ConsensusPoller checks the consensus state for each member of a BackendGroup -// resolves the highest common block for multiple nodes, and reconciles the consensus -// in case of block hash divergence to minimize re-orgs -type ConsensusPoller struct { - ctx context.Context - cancelFunc context.CancelFunc - listeners []OnConsensusBroken - - backendGroup *BackendGroup - backendState map[*Backend]*backendState - consensusGroupMux sync.Mutex - consensusGroup []*Backend - - tracker ConsensusTracker - asyncHandler ConsensusAsyncHandler - - minPeerCount uint64 - banPeriod time.Duration - maxUpdateThreshold time.Duration - maxBlockLag uint64 - maxBlockRange uint64 - interval time.Duration -} - -type backendState struct { - backendStateMux sync.Mutex - - latestBlockNumber hexutil.Uint64 - latestBlockHash string - safeBlockNumber hexutil.Uint64 - finalizedBlockNumber hexutil.Uint64 - - peerCount uint64 - inSync bool - - lastUpdate time.Time - - bannedUntil time.Time -} - -func (bs *backendState) IsBanned() bool { - return time.Now().Before(bs.bannedUntil) -} - -// GetConsensusGroup returns the backend members that are agreeing in a consensus -func (cp *ConsensusPoller) GetConsensusGroup() []*Backend { - defer cp.consensusGroupMux.Unlock() - cp.consensusGroupMux.Lock() - - g := make([]*Backend, len(cp.consensusGroup)) - copy(g, cp.consensusGroup) - - return g -} - -// GetLatestBlockNumber returns the `latest` agreed block number in a consensus -func (ct *ConsensusPoller) GetLatestBlockNumber() hexutil.Uint64 { - return ct.tracker.GetLatestBlockNumber() -} - -// GetSafeBlockNumber returns the `safe` agreed block number in a consensus -func (ct *ConsensusPoller) GetSafeBlockNumber() hexutil.Uint64 { - return ct.tracker.GetSafeBlockNumber() -} - -// GetFinalizedBlockNumber returns the `finalized` agreed block number in a consensus -func (ct *ConsensusPoller) GetFinalizedBlockNumber() hexutil.Uint64 { - return ct.tracker.GetFinalizedBlockNumber() -} - -func (cp *ConsensusPoller) Shutdown() { - cp.asyncHandler.Shutdown() -} - -// ConsensusAsyncHandler controls the asynchronous polling mechanism, interval and shutdown -type ConsensusAsyncHandler interface { - Init() - Shutdown() -} - -// NoopAsyncHandler allows fine control updating the consensus -type NoopAsyncHandler struct{} - -func NewNoopAsyncHandler() ConsensusAsyncHandler { - log.Warn("using NewNoopAsyncHandler") - return &NoopAsyncHandler{} -} -func (ah *NoopAsyncHandler) Init() {} -func (ah *NoopAsyncHandler) Shutdown() {} - -// PollerAsyncHandler asynchronously updates each individual backend and the group consensus -type PollerAsyncHandler struct { - ctx context.Context - cp *ConsensusPoller -} - -func NewPollerAsyncHandler(ctx context.Context, cp *ConsensusPoller) ConsensusAsyncHandler { - return &PollerAsyncHandler{ - ctx: ctx, - cp: cp, - } -} -func (ah *PollerAsyncHandler) Init() { - // create the individual backend pollers. - log.Info("total number of primary candidates", "primaries", len(ah.cp.backendGroup.Primaries())) - log.Info("total number of fallback candidates", "fallbacks", len(ah.cp.backendGroup.Fallbacks())) - - for _, be := range ah.cp.backendGroup.Primaries() { - go func(be *Backend) { - for { - timer := time.NewTimer(ah.cp.interval) - ah.cp.UpdateBackend(ah.ctx, be) - select { - case <-timer.C: - case <-ah.ctx.Done(): - timer.Stop() - return - } - } - }(be) - } - - for _, be := range ah.cp.backendGroup.Fallbacks() { - go func(be *Backend) { - for { - timer := time.NewTimer(ah.cp.interval) - - healthyCandidates := ah.cp.FilterCandidates(ah.cp.backendGroup.Primaries()) - - log.Info("number of healthy primary candidates", "healthy_candidates", len(healthyCandidates)) - if len(healthyCandidates) == 0 { - log.Debug("zero healthy candidates, querying fallback backend", - "backend_name", be.Name) - ah.cp.UpdateBackend(ah.ctx, be) - } - - select { - case <-timer.C: - case <-ah.ctx.Done(): - timer.Stop() - return - } - } - }(be) - } - - // create the group consensus poller - go func() { - for { - timer := time.NewTimer(ah.cp.interval) - log.Info("updating backend group consensus") - ah.cp.UpdateBackendGroupConsensus(ah.ctx) - - select { - case <-timer.C: - case <-ah.ctx.Done(): - timer.Stop() - return - } - } - }() -} -func (ah *PollerAsyncHandler) Shutdown() { - ah.cp.cancelFunc() -} - -type ConsensusOpt func(cp *ConsensusPoller) - -func WithTracker(tracker ConsensusTracker) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.tracker = tracker - } -} - -func WithAsyncHandler(asyncHandler ConsensusAsyncHandler) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.asyncHandler = asyncHandler - } -} - -func WithListener(listener OnConsensusBroken) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.AddListener(listener) - } -} - -func (cp *ConsensusPoller) AddListener(listener OnConsensusBroken) { - cp.listeners = append(cp.listeners, listener) -} - -func (cp *ConsensusPoller) ClearListeners() { - cp.listeners = []OnConsensusBroken{} -} - -func WithBanPeriod(banPeriod time.Duration) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.banPeriod = banPeriod - } -} - -func WithMaxUpdateThreshold(maxUpdateThreshold time.Duration) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.maxUpdateThreshold = maxUpdateThreshold - } -} - -func WithMaxBlockLag(maxBlockLag uint64) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.maxBlockLag = maxBlockLag - } -} - -func WithMaxBlockRange(maxBlockRange uint64) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.maxBlockRange = maxBlockRange - } -} - -func WithMinPeerCount(minPeerCount uint64) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.minPeerCount = minPeerCount - } -} - -func WithPollerInterval(interval time.Duration) ConsensusOpt { - return func(cp *ConsensusPoller) { - cp.interval = interval - } -} - -func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller { - ctx, cancelFunc := context.WithCancel(context.Background()) - - state := make(map[*Backend]*backendState, len(bg.Backends)) - - cp := &ConsensusPoller{ - ctx: ctx, - cancelFunc: cancelFunc, - backendGroup: bg, - backendState: state, - - banPeriod: 5 * time.Minute, - maxUpdateThreshold: 30 * time.Second, - maxBlockLag: 8, // 8*12 seconds = 96 seconds ~ 1.6 minutes - minPeerCount: 3, - interval: DefaultPollerInterval, - } - - for _, opt := range opts { - opt(cp) - } - - if cp.tracker == nil { - cp.tracker = NewInMemoryConsensusTracker() - } - - if cp.asyncHandler == nil { - cp.asyncHandler = NewPollerAsyncHandler(ctx, cp) - } - - cp.Reset() - cp.asyncHandler.Init() - - return cp -} - -// UpdateBackend refreshes the consensus state of a single backend -func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { - bs := cp.getBackendState(be) - RecordConsensusBackendBanned(be, bs.IsBanned()) - - if bs.IsBanned() { - log.Debug("skipping backend - banned", "backend", be.Name) - return - } - - // if backend is not healthy state we'll only resume checking it after ban - if !be.IsHealthy() && !be.forcedCandidate { - log.Warn("backend banned - not healthy", "backend", be.Name) - cp.Ban(be) - return - } - - inSync, err := cp.isInSync(ctx, be) - RecordConsensusBackendInSync(be, err == nil && inSync) - if err != nil { - log.Warn("error updating backend sync state", "name", be.Name, "err", err) - } - - var peerCount uint64 - if !be.skipPeerCountCheck { - peerCount, err = cp.getPeerCount(ctx, be) - if err != nil { - log.Warn("error updating backend peer count", "name", be.Name, "err", err) - } - RecordConsensusBackendPeerCount(be, peerCount) - } - - latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") - if err != nil { - log.Warn("error updating backend - latest block", "name", be.Name, "err", err) - } - - safeBlockNumber, _, err := cp.fetchBlock(ctx, be, "safe") - if err != nil { - log.Warn("error updating backend - safe block", "name", be.Name, "err", err) - } - - finalizedBlockNumber, _, err := cp.fetchBlock(ctx, be, "finalized") - if err != nil { - log.Warn("error updating backend - finalized block", "name", be.Name, "err", err) - } - - RecordConsensusBackendUpdateDelay(be, bs.lastUpdate) - - changed := cp.setBackendState(be, peerCount, inSync, - latestBlockNumber, latestBlockHash, - safeBlockNumber, finalizedBlockNumber) - - RecordBackendLatestBlock(be, latestBlockNumber) - RecordBackendSafeBlock(be, safeBlockNumber) - RecordBackendFinalizedBlock(be, finalizedBlockNumber) - - if changed { - log.Debug("backend state updated", - "name", be.Name, - "peerCount", peerCount, - "inSync", inSync, - "latestBlockNumber", latestBlockNumber, - "latestBlockHash", latestBlockHash, - "safeBlockNumber", safeBlockNumber, - "finalizedBlockNumber", finalizedBlockNumber, - "lastUpdate", bs.lastUpdate) - } - - // sanity check for latest, safe and finalized block tags - expectedBlockTags := cp.checkExpectedBlockTags( - latestBlockNumber, - bs.safeBlockNumber, safeBlockNumber, - bs.finalizedBlockNumber, finalizedBlockNumber) - - RecordBackendUnexpectedBlockTags(be, !expectedBlockTags) - - if !expectedBlockTags && !be.forcedCandidate { - log.Warn("backend banned - unexpected block tags", - "backend", be.Name, - "oldFinalized", bs.finalizedBlockNumber, - "finalizedBlockNumber", finalizedBlockNumber, - "oldSafe", bs.safeBlockNumber, - "safeBlockNumber", safeBlockNumber, - "latestBlockNumber", latestBlockNumber, - ) - cp.Ban(be) - } -} - -// checkExpectedBlockTags for unexpected conditions on block tags -// - finalized block number should never decrease -// - safe block number should never decrease -// - finalized block should be <= safe block <= latest block -func (cp *ConsensusPoller) checkExpectedBlockTags( - currentLatest hexutil.Uint64, - oldSafe hexutil.Uint64, currentSafe hexutil.Uint64, - oldFinalized hexutil.Uint64, currentFinalized hexutil.Uint64) bool { - return currentFinalized >= oldFinalized && - currentSafe >= oldSafe && - currentFinalized <= currentSafe && - currentSafe <= currentLatest -} - -// UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends -func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { - // get the latest block number from the tracker - currentConsensusBlockNumber := cp.GetLatestBlockNumber() - - // get the candidates for the consensus group - candidates := cp.getConsensusCandidates() - - // update the lowest latest block number and hash - // the lowest safe block number - // the lowest finalized block number - var lowestLatestBlock hexutil.Uint64 - var lowestLatestBlockHash string - var lowestFinalizedBlock hexutil.Uint64 - var lowestSafeBlock hexutil.Uint64 - for _, bs := range candidates { - if lowestLatestBlock == 0 || bs.latestBlockNumber < lowestLatestBlock { - lowestLatestBlock = bs.latestBlockNumber - lowestLatestBlockHash = bs.latestBlockHash - } - if lowestFinalizedBlock == 0 || bs.finalizedBlockNumber < lowestFinalizedBlock { - lowestFinalizedBlock = bs.finalizedBlockNumber - } - if lowestSafeBlock == 0 || bs.safeBlockNumber < lowestSafeBlock { - lowestSafeBlock = bs.safeBlockNumber - } - } - - // find the proposed block among the candidates - // the proposed block needs have the same hash in the entire consensus group - proposedBlock := lowestLatestBlock - proposedBlockHash := lowestLatestBlockHash - hasConsensus := false - broken := false - - if lowestLatestBlock > currentConsensusBlockNumber { - log.Debug("validating consensus on block", "lowestLatestBlock", lowestLatestBlock) - } - - // if there is a block to propose, check if it is the same in all backends - if proposedBlock > 0 { - for !hasConsensus { - allAgreed := true - for be := range candidates { - actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock.String()) - if err != nil { - log.Warn("error updating backend", "name", be.Name, "err", err) - continue - } - if proposedBlockHash == "" { - proposedBlockHash = actualBlockHash - } - blocksDontMatch := (actualBlockNumber != proposedBlock) || (actualBlockHash != proposedBlockHash) - if blocksDontMatch { - if currentConsensusBlockNumber >= actualBlockNumber { - log.Warn("backend broke consensus", - "name", be.Name, - "actualBlockNumber", actualBlockNumber, - "actualBlockHash", actualBlockHash, - "proposedBlock", proposedBlock, - "proposedBlockHash", proposedBlockHash) - broken = true - } - allAgreed = false - break - } - } - if allAgreed { - hasConsensus = true - } else { - // walk one block behind and try again - proposedBlock -= 1 - proposedBlockHash = "" - log.Debug("no consensus, now trying", "block:", proposedBlock) - } - } - } - - if broken { - // propagate event to other interested parts, such as cache invalidator - for _, l := range cp.listeners { - l() - } - log.Info("consensus broken", - "currentConsensusBlockNumber", currentConsensusBlockNumber, - "proposedBlock", proposedBlock, - "proposedBlockHash", proposedBlockHash) - } - - // update tracker - cp.tracker.SetLatestBlockNumber(proposedBlock) - cp.tracker.SetSafeBlockNumber(lowestSafeBlock) - cp.tracker.SetFinalizedBlockNumber(lowestFinalizedBlock) - - // update consensus group - group := make([]*Backend, 0, len(candidates)) - consensusBackendsNames := make([]string, 0, len(candidates)) - filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) - for _, be := range cp.backendGroup.Backends { - _, exist := candidates[be] - if exist { - group = append(group, be) - consensusBackendsNames = append(consensusBackendsNames, be.Name) - } else { - filteredBackendsNames = append(filteredBackendsNames, be.Name) - } - } - - cp.consensusGroupMux.Lock() - cp.consensusGroup = group - cp.consensusGroupMux.Unlock() - - RecordGroupConsensusLatestBlock(cp.backendGroup, proposedBlock) - RecordGroupConsensusSafeBlock(cp.backendGroup, lowestSafeBlock) - RecordGroupConsensusFinalizedBlock(cp.backendGroup, lowestFinalizedBlock) - - RecordGroupConsensusCount(cp.backendGroup, len(group)) - RecordGroupConsensusFilteredCount(cp.backendGroup, len(filteredBackendsNames)) - RecordGroupTotalCount(cp.backendGroup, len(cp.backendGroup.Backends)) - - log.Debug("group state", - "proposedBlock", proposedBlock, - "consensusBackends", strings.Join(consensusBackendsNames, ", "), - "filteredBackends", strings.Join(filteredBackendsNames, ", ")) -} - -// IsBanned checks if a specific backend is banned -func (cp *ConsensusPoller) IsBanned(be *Backend) bool { - bs := cp.backendState[be] - defer bs.backendStateMux.Unlock() - bs.backendStateMux.Lock() - return bs.IsBanned() -} - -// Ban bans a specific backend -func (cp *ConsensusPoller) Ban(be *Backend) { - if be.forcedCandidate { - return - } - - bs := cp.backendState[be] - defer bs.backendStateMux.Unlock() - bs.backendStateMux.Lock() - bs.bannedUntil = time.Now().Add(cp.banPeriod) - - // when we ban a node, we give it the chance to start from any block when it is back - bs.latestBlockNumber = 0 - bs.safeBlockNumber = 0 - bs.finalizedBlockNumber = 0 -} - -// Unban removes any bans from the backends -func (cp *ConsensusPoller) Unban(be *Backend) { - bs := cp.backendState[be] - defer bs.backendStateMux.Unlock() - bs.backendStateMux.Lock() - bs.bannedUntil = time.Now().Add(-10 * time.Hour) -} - -// Reset reset all backend states -func (cp *ConsensusPoller) Reset() { - for _, be := range cp.backendGroup.Backends { - cp.backendState[be] = &backendState{} - } -} - -// fetchBlock is a convenient wrapper to make a request to get a block directly from the backend -func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber hexutil.Uint64, blockHash string, err error) { - var rpcRes RPCRes - err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_getBlockByNumber", block, false) - if err != nil { - return 0, "", err - } - - jsonMap, ok := rpcRes.Result.(map[string]interface{}) - if !ok { - return 0, "", fmt.Errorf("unexpected response to eth_getBlockByNumber on backend %s", be.Name) - } - blockNumber = hexutil.Uint64(hexutil.MustDecodeUint64(jsonMap["number"].(string))) - blockHash = jsonMap["hash"].(string) - - return -} - -// getPeerCount is a convenient wrapper to retrieve the current peer count from the backend -func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count uint64, err error) { - var rpcRes RPCRes - err = be.ForwardRPC(ctx, &rpcRes, "67", "net_peerCount") - if err != nil { - return 0, err - } - - jsonMap, ok := rpcRes.Result.(string) - if !ok { - return 0, fmt.Errorf("unexpected response to net_peerCount on backend %s", be.Name) - } - - count = hexutil.MustDecodeUint64(jsonMap) - - return count, nil -} - -// isInSync is a convenient wrapper to check if the backend is in sync from the network -func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bool, err error) { - var rpcRes RPCRes - err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_syncing") - if err != nil { - return false, err - } - - var res bool - switch typed := rpcRes.Result.(type) { - case bool: - syncing := typed - res = !syncing - case string: - syncing, err := strconv.ParseBool(typed) - if err != nil { - return false, err - } - res = !syncing - default: - // result is a json when not in sync - res = false - } - - return res, nil -} - -// getBackendState creates a copy of backend state so that the caller can use it without locking -func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { - bs := cp.backendState[be] - defer bs.backendStateMux.Unlock() - bs.backendStateMux.Lock() - - return &backendState{ - latestBlockNumber: bs.latestBlockNumber, - latestBlockHash: bs.latestBlockHash, - safeBlockNumber: bs.safeBlockNumber, - finalizedBlockNumber: bs.finalizedBlockNumber, - peerCount: bs.peerCount, - inSync: bs.inSync, - lastUpdate: bs.lastUpdate, - bannedUntil: bs.bannedUntil, - } -} - -func (cp *ConsensusPoller) GetLastUpdate(be *Backend) time.Time { - bs := cp.backendState[be] - defer bs.backendStateMux.Unlock() - bs.backendStateMux.Lock() - return bs.lastUpdate -} - -func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, - latestBlockNumber hexutil.Uint64, latestBlockHash string, - safeBlockNumber hexutil.Uint64, - finalizedBlockNumber hexutil.Uint64) bool { - bs := cp.backendState[be] - bs.backendStateMux.Lock() - changed := bs.latestBlockHash != latestBlockHash - bs.peerCount = peerCount - bs.inSync = inSync - bs.latestBlockNumber = latestBlockNumber - bs.latestBlockHash = latestBlockHash - bs.finalizedBlockNumber = finalizedBlockNumber - bs.safeBlockNumber = safeBlockNumber - bs.lastUpdate = time.Now() - bs.backendStateMux.Unlock() - return changed -} - -// getConsensusCandidates will search for candidates in the primary group, -// if there are none it will search for candidates in he fallback group -func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { - - healthyPrimaries := cp.FilterCandidates(cp.backendGroup.Primaries()) - - RecordHealthyCandidates(cp.backendGroup, len(healthyPrimaries)) - if len(healthyPrimaries) > 0 { - return healthyPrimaries - } - - return cp.FilterCandidates(cp.backendGroup.Fallbacks()) -} - -// filterCandidates find out what backends are the candidates to be in the consensus group -// and create a copy of current their state -// -// a candidate is a serving node within the following conditions: -// - not banned -// - healthy (network latency and error rate) -// - with minimum peer count -// - in sync -// - updated recently -// - not lagging latest block -func (cp *ConsensusPoller) FilterCandidates(backends []*Backend) map[*Backend]*backendState { - - candidates := make(map[*Backend]*backendState, len(cp.backendGroup.Backends)) - - for _, be := range backends { - - bs := cp.getBackendState(be) - if be.forcedCandidate { - candidates[be] = bs - continue - } - if bs.IsBanned() { - continue - } - if !be.IsHealthy() { - continue - } - if !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount { - log.Debug("backend peer count too low for inclusion in consensus", - "backend_name", be.Name, - "peer_count", bs.peerCount, - "min_peer_count", cp.minPeerCount, - ) - continue - } - if !bs.inSync { - continue - } - if bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { - continue - } - - candidates[be] = bs - } - - // find the highest block, in order to use it defining the highest non-lagging ancestor block - var highestLatestBlock hexutil.Uint64 - for _, bs := range candidates { - if bs.latestBlockNumber > highestLatestBlock { - highestLatestBlock = bs.latestBlockNumber - } - } - - // find the highest common ancestor block - lagging := make([]*Backend, 0, len(candidates)) - for be, bs := range candidates { - // check if backend is lagging behind the highest block - if uint64(highestLatestBlock-bs.latestBlockNumber) > cp.maxBlockLag { - lagging = append(lagging, be) - } - } - - // remove lagging backends from the candidates - for _, be := range lagging { - delete(candidates, be) - } - - return candidates -}
diff --git OP/proxyd/consensus_tracker.go CELO/proxyd/consensus_tracker.go deleted file mode 100644 index 77e0fdba99125b1686de6cd83162be6cedfa7936..0000000000000000000000000000000000000000 --- OP/proxyd/consensus_tracker.go +++ /dev/null @@ -1,356 +0,0 @@ -package proxyd - -import ( - "context" - "encoding/json" - "fmt" - "os" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" - "github.com/go-redsync/redsync/v4" - "github.com/go-redsync/redsync/v4/redis/goredis/v9" - "github.com/redis/go-redis/v9" -) - -// ConsensusTracker abstracts how we store and retrieve the current consensus -// allowing it to be stored locally in-memory or in a shared Redis cluster -type ConsensusTracker interface { - GetLatestBlockNumber() hexutil.Uint64 - SetLatestBlockNumber(blockNumber hexutil.Uint64) - GetSafeBlockNumber() hexutil.Uint64 - SetSafeBlockNumber(blockNumber hexutil.Uint64) - GetFinalizedBlockNumber() hexutil.Uint64 - SetFinalizedBlockNumber(blockNumber hexutil.Uint64) -} - -// DTO to hold the current consensus state -type ConsensusTrackerState struct { - Latest hexutil.Uint64 `json:"latest"` - Safe hexutil.Uint64 `json:"safe"` - Finalized hexutil.Uint64 `json:"finalized"` -} - -func (ct *InMemoryConsensusTracker) update(o *ConsensusTrackerState) { - ct.mutex.Lock() - defer ct.mutex.Unlock() - - ct.state.Latest = o.Latest - ct.state.Safe = o.Safe - ct.state.Finalized = o.Finalized -} - -// InMemoryConsensusTracker store and retrieve in memory, async-safe -type InMemoryConsensusTracker struct { - mutex sync.Mutex - state *ConsensusTrackerState -} - -func NewInMemoryConsensusTracker() ConsensusTracker { - return &InMemoryConsensusTracker{ - mutex: sync.Mutex{}, - state: &ConsensusTrackerState{}, - } -} - -func (ct *InMemoryConsensusTracker) Valid() bool { - return ct.GetLatestBlockNumber() > 0 && - ct.GetSafeBlockNumber() > 0 && - ct.GetFinalizedBlockNumber() > 0 -} - -func (ct *InMemoryConsensusTracker) Behind(other *InMemoryConsensusTracker) bool { - return ct.GetLatestBlockNumber() < other.GetLatestBlockNumber() || - ct.GetSafeBlockNumber() < other.GetSafeBlockNumber() || - ct.GetFinalizedBlockNumber() < other.GetFinalizedBlockNumber() -} - -func (ct *InMemoryConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { - defer ct.mutex.Unlock() - ct.mutex.Lock() - - return ct.state.Latest -} - -func (ct *InMemoryConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64) { - defer ct.mutex.Unlock() - ct.mutex.Lock() - - ct.state.Latest = blockNumber -} - -func (ct *InMemoryConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { - defer ct.mutex.Unlock() - ct.mutex.Lock() - - return ct.state.Safe -} - -func (ct *InMemoryConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { - defer ct.mutex.Unlock() - ct.mutex.Lock() - - ct.state.Safe = blockNumber -} - -func (ct *InMemoryConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { - defer ct.mutex.Unlock() - ct.mutex.Lock() - - return ct.state.Finalized -} - -func (ct *InMemoryConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { - defer ct.mutex.Unlock() - ct.mutex.Lock() - - ct.state.Finalized = blockNumber -} - -// RedisConsensusTracker store and retrieve in a shared Redis cluster, with leader election -type RedisConsensusTracker struct { - ctx context.Context - client *redis.Client - namespace string - backendGroup *BackendGroup - - redlock *redsync.Mutex - lockPeriod time.Duration - heartbeatInterval time.Duration - - leader bool - leaderName string - - // holds the state collected by local pollers - local *InMemoryConsensusTracker - - // holds a copy of the remote shared state - // when leader, updates the remote with the local state - remote *InMemoryConsensusTracker -} - -type RedisConsensusTrackerOpt func(cp *RedisConsensusTracker) - -func WithLockPeriod(lockPeriod time.Duration) RedisConsensusTrackerOpt { - return func(ct *RedisConsensusTracker) { - ct.lockPeriod = lockPeriod - } -} - -func WithHeartbeatInterval(heartbeatInterval time.Duration) RedisConsensusTrackerOpt { - return func(ct *RedisConsensusTracker) { - ct.heartbeatInterval = heartbeatInterval - } -} -func NewRedisConsensusTracker(ctx context.Context, - redisClient *redis.Client, - bg *BackendGroup, - namespace string, - opts ...RedisConsensusTrackerOpt) ConsensusTracker { - - tracker := &RedisConsensusTracker{ - ctx: ctx, - client: redisClient, - backendGroup: bg, - namespace: namespace, - - lockPeriod: 30 * time.Second, - heartbeatInterval: 2 * time.Second, - local: NewInMemoryConsensusTracker().(*InMemoryConsensusTracker), - remote: NewInMemoryConsensusTracker().(*InMemoryConsensusTracker), - } - - for _, opt := range opts { - opt(tracker) - } - - return tracker -} - -func (ct *RedisConsensusTracker) Init() { - go func() { - for { - timer := time.NewTimer(ct.heartbeatInterval) - ct.stateHeartbeat() - - select { - case <-timer.C: - continue - case <-ct.ctx.Done(): - timer.Stop() - return - } - } - }() -} - -func (ct *RedisConsensusTracker) stateHeartbeat() { - pool := goredis.NewPool(ct.client) - rs := redsync.New(pool) - key := ct.key("mutex") - - val, err := ct.client.Get(ct.ctx, key).Result() - if err != nil && err != redis.Nil { - log.Error("failed to read the lock", "err", err) - RecordGroupConsensusError(ct.backendGroup, "read_lock", err) - if ct.leader { - ok, err := ct.redlock.Unlock() - if err != nil || !ok { - log.Error("failed to release the lock after error", "err", err) - RecordGroupConsensusError(ct.backendGroup, "leader_release_lock", err) - return - } - ct.leader = false - } - return - } - if val != "" { - if ct.leader { - log.Debug("extending lock") - ok, err := ct.redlock.Extend() - if err != nil || !ok { - log.Error("failed to extend lock", "err", err, "mutex", ct.redlock.Name(), "val", ct.redlock.Value()) - RecordGroupConsensusError(ct.backendGroup, "leader_extend_lock", err) - ok, err := ct.redlock.Unlock() - if err != nil || !ok { - log.Error("failed to release the lock after error", "err", err) - RecordGroupConsensusError(ct.backendGroup, "leader_release_lock", err) - return - } - ct.leader = false - return - } - ct.postPayload(val) - } else { - // retrieve current leader - leaderName, err := ct.client.Get(ct.ctx, ct.key(fmt.Sprintf("leader:%s", val))).Result() - if err != nil && err != redis.Nil { - log.Error("failed to read the remote leader", "err", err) - RecordGroupConsensusError(ct.backendGroup, "read_leader", err) - return - } - ct.leaderName = leaderName - log.Debug("following", "val", val, "leader", leaderName) - // retrieve payload - val, err := ct.client.Get(ct.ctx, ct.key(fmt.Sprintf("state:%s", val))).Result() - if err != nil && err != redis.Nil { - log.Error("failed to read the remote state", "err", err) - RecordGroupConsensusError(ct.backendGroup, "read_state", err) - return - } - if val == "" { - log.Error("remote state is missing (recent leader election maybe?)") - RecordGroupConsensusError(ct.backendGroup, "read_state_missing", err) - return - } - state := &ConsensusTrackerState{} - err = json.Unmarshal([]byte(val), state) - if err != nil { - log.Error("failed to unmarshal the remote state", "err", err) - RecordGroupConsensusError(ct.backendGroup, "read_unmarshal_state", err) - return - } - - ct.remote.update(state) - log.Debug("updated state from remote", "state", val, "leader", leaderName) - - RecordGroupConsensusHALatestBlock(ct.backendGroup, leaderName, ct.remote.state.Latest) - RecordGroupConsensusHASafeBlock(ct.backendGroup, leaderName, ct.remote.state.Safe) - RecordGroupConsensusHAFinalizedBlock(ct.backendGroup, leaderName, ct.remote.state.Finalized) - } - } else { - if !ct.local.Valid() { - log.Warn("local state is not valid or behind remote, skipping") - return - } - if ct.remote.Valid() && ct.local.Behind(ct.remote) { - log.Warn("local state is behind remote, skipping") - return - } - - log.Info("lock not found, creating a new one") - - mutex := rs.NewMutex(key, - redsync.WithExpiry(ct.lockPeriod), - redsync.WithFailFast(true), - redsync.WithTries(1)) - - // nosemgrep: missing-unlock-before-return - // this lock is hold indefinitely, and it is extended until the leader dies - if err := mutex.Lock(); err != nil { - log.Debug("failed to obtain lock", "err", err) - ct.leader = false - return - } - - log.Info("lock acquired", "mutex", mutex.Name(), "val", mutex.Value()) - ct.redlock = mutex - ct.leader = true - ct.postPayload(mutex.Value()) - } -} - -func (ct *RedisConsensusTracker) key(tag string) string { - return fmt.Sprintf("consensus:%s:%s", ct.namespace, tag) -} - -func (ct *RedisConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { - return ct.remote.GetLatestBlockNumber() -} - -func (ct *RedisConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64) { - ct.local.SetLatestBlockNumber(blockNumber) -} - -func (ct *RedisConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { - return ct.remote.GetSafeBlockNumber() -} - -func (ct *RedisConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { - ct.local.SetSafeBlockNumber(blockNumber) -} - -func (ct *RedisConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { - return ct.remote.GetFinalizedBlockNumber() -} - -func (ct *RedisConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { - ct.local.SetFinalizedBlockNumber(blockNumber) -} - -func (ct *RedisConsensusTracker) postPayload(mutexVal string) { - jsonState, err := json.Marshal(ct.local.state) - if err != nil { - log.Error("failed to marshal local", "err", err) - RecordGroupConsensusError(ct.backendGroup, "leader_marshal_local_state", err) - ct.leader = false - return - } - err = ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("state:%s", mutexVal)), jsonState, ct.lockPeriod).Err() - if err != nil { - log.Error("failed to post the state", "err", err) - RecordGroupConsensusError(ct.backendGroup, "leader_post_state", err) - ct.leader = false - return - } - - leader, _ := os.LookupEnv("HOSTNAME") - err = ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("leader:%s", mutexVal)), leader, ct.lockPeriod).Err() - if err != nil { - log.Error("failed to post the leader", "err", err) - RecordGroupConsensusError(ct.backendGroup, "leader_post_leader", err) - ct.leader = false - return - } - - log.Debug("posted state", "state", string(jsonState), "leader", leader) - - ct.leaderName = leader - ct.remote.update(ct.local.state) - - RecordGroupConsensusHALatestBlock(ct.backendGroup, leader, ct.remote.state.Latest) - RecordGroupConsensusHASafeBlock(ct.backendGroup, leader, ct.remote.state.Safe) - RecordGroupConsensusHAFinalizedBlock(ct.backendGroup, leader, ct.remote.state.Finalized) -}
diff --git OP/proxyd/entrypoint.sh CELO/proxyd/entrypoint.sh deleted file mode 100644 index ef83fa8e47d4f3383e698c9faf2b99d1d7c30f01..0000000000000000000000000000000000000000 --- OP/proxyd/entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -echo "Updating CA certificates." -update-ca-certificates -echo "Running CMD." -exec "$@" \ No newline at end of file
(deleted)
+0
-7
diff --git OP/proxyd/errors.go CELO/proxyd/errors.go deleted file mode 100644 index 51f8df6ddebb137aa5cfdd57611df6677101fab6..0000000000000000000000000000000000000000 --- OP/proxyd/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package proxyd - -import "fmt" - -func wrapErr(err error, msg string) error { - return fmt.Errorf("%s %w", msg, err) -}
diff --git OP/proxyd/example.config.toml CELO/proxyd/example.config.toml deleted file mode 100644 index b54b342f5189b944c1794d1595cfb58a7410eed7..0000000000000000000000000000000000000000 --- OP/proxyd/example.config.toml +++ /dev/null @@ -1,123 +0,0 @@ -# List of WS methods to whitelist. -ws_method_whitelist = [ - "eth_subscribe", - "eth_call", - "eth_chainId" -] -# Enable WS on this backend group. There can only be one WS-enabled backend group. -ws_backend_group = "main" - -[server] -# Host for the proxyd RPC server to listen on. -rpc_host = "0.0.0.0" -# Port for the above. -rpc_port = 8080 -# Host for the proxyd WS server to listen on. -ws_host = "0.0.0.0" -# Port for the above -# Set the ws_port to 0 to disable WS -ws_port = 8085 -# Maximum client body size, in bytes, that the server will accept. -max_body_size_bytes = 10485760 -max_concurrent_rpcs = 1000 -# Server log level -log_level = "info" - -[redis] -# URL to a Redis instance. -url = "redis://localhost:6379" - -[metrics] -# Whether or not to enable Prometheus metrics. -enabled = true -# Host for the Prometheus metrics endpoint to listen on. -host = "0.0.0.0" -# Port for the above. -port = 9761 - -[backend] -# How long proxyd should wait for a backend response before timing out. -response_timeout_seconds = 5 -# Maximum response size, in bytes, that proxyd will accept from a backend. -max_response_size_bytes = 5242880 -# Maximum number of times proxyd will try a backend before giving up. -max_retries = 3 -# Number of seconds to wait before trying an unhealthy backend again. -out_of_service_seconds = 600 -# Maximum latency accepted to serve requests, default 10s -max_latency_threshold = "30s" -# Maximum latency accepted to serve requests before degraded, default 5s -max_degraded_latency_threshold = "10s" -# Maximum error rate accepted to serve requests, default 0.5 (i.e. 50%) -max_error_rate_threshold = 0.3 - -[backends] -# A map of backends by name. -[backends.infura] -# The URL to contact the backend at. Will be read from the environment -# if an environment variable prefixed with $ is provided. -rpc_url = "" -# The WS URL to contact the backend at. Will be read from the environment -# if an environment variable prefixed with $ is provided. -ws_url = "" -username = "" -# An HTTP Basic password to authenticate with the backend. Will be read from -# the environment if an environment variable prefixed with $ is provided. -password = "" -max_rps = 3 -max_ws_conns = 1 -# Path to a custom root CA. -ca_file = "" -# Path to a custom client cert file. -client_cert_file = "" -# Path to a custom client key file. -client_key_file = "" -# Allows backends to skip peer count checking, default false -# consensus_skip_peer_count = true -# Specified the target method to get receipts, default "debug_getRawReceipts" -# See https://github.com/ethereum-optimism/optimism/blob/186e46a47647a51a658e699e9ff047d39444c2de/op-node/sources/receipts.go#L186-L253 -consensus_receipts_target = "eth_getBlockReceipts" - -[backends.alchemy] -rpc_url = "" -ws_url = "" -username = "" -password = "" -max_rps = 3 -max_ws_conns = 1 -consensus_receipts_target = "alchemy_getTransactionReceipts" - -[backend_groups] -[backend_groups.main] -backends = ["infura"] -# Enable consensus awareness for backend group, making it act as a load balancer, default false -# consensus_aware = true -# Period in which the backend wont serve requests if banned, default 5m -# consensus_ban_period = "1m" -# Maximum delay for update the backend, default 30s -# consensus_max_update_threshold = "20s" -# Maximum block lag, default 8 -# consensus_max_block_lag = 16 -# Maximum block range (for eth_getLogs method), no default -# consensus_max_block_range = 20000 -# Minimum peer count, default 3 -# consensus_min_peer_count = 4 - -[backend_groups.alchemy] -backends = ["alchemy"] - -# If the authentication group below is in the config, -# proxyd will only accept authenticated requests. -[authentication] -# Mapping of auth key to alias. The alias is used to provide a human- -# readable name for the auth key in monitoring. The auth key will be -# read from the environment if an environment variable prefixed with $ -# is provided. Note that you will need to quote the environment variable -# in order for it to be value TOML, e.g. "$FOO_AUTH_KEY" = "foo_alias". -secret = "test" - -# Mapping of methods to backend groups. -[rpc_method_mappings] -eth_call = "main" -eth_chainId = "main" -eth_blockNumber = "alchemy"
diff --git OP/proxyd/frontend_rate_limiter.go CELO/proxyd/frontend_rate_limiter.go deleted file mode 100644 index d0590f0561da160d7a1846980d19ffc247056a01..0000000000000000000000000000000000000000 --- OP/proxyd/frontend_rate_limiter.go +++ /dev/null @@ -1,139 +0,0 @@ -package proxyd - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/redis/go-redis/v9" -) - -type FrontendRateLimiter interface { - // Take consumes a key, and a maximum number of requests - // per time interval. It returns a boolean denoting if - // the limit could be taken, or an error if a failure - // occurred in the backing rate limit implementation. - // - // No error will be returned if the limit could not be taken - // as a result of the requestor being over the limit. - Take(ctx context.Context, key string) (bool, error) -} - -// limitedKeys is a wrapper around a map that stores a truncated -// timestamp and a mutex. The map is used to keep track of rate -// limit keys, and their used limits. -type limitedKeys struct { - truncTS int64 - keys map[string]int - mtx sync.Mutex -} - -func newLimitedKeys(t int64) *limitedKeys { - return &limitedKeys{ - truncTS: t, - keys: make(map[string]int), - } -} - -func (l *limitedKeys) Take(key string, max int) bool { - l.mtx.Lock() - defer l.mtx.Unlock() - val, ok := l.keys[key] - if !ok { - l.keys[key] = 0 - val = 0 - } - l.keys[key] = val + 1 - return val < max -} - -// MemoryFrontendRateLimiter is a rate limiter that stores -// all rate limiting information in local memory. It works -// by storing a limitedKeys struct that references the -// truncated timestamp at which the struct was created. If -// the current truncated timestamp doesn't match what's -// referenced, the limit is reset. Otherwise, values in -// a map are incremented to represent the limit. -type MemoryFrontendRateLimiter struct { - currGeneration *limitedKeys - dur time.Duration - max int - mtx sync.Mutex -} - -func NewMemoryFrontendRateLimit(dur time.Duration, max int) FrontendRateLimiter { - return &MemoryFrontendRateLimiter{ - dur: dur, - max: max, - } -} - -func (m *MemoryFrontendRateLimiter) Take(ctx context.Context, key string) (bool, error) { - m.mtx.Lock() - // Create truncated timestamp - truncTS := truncateNow(m.dur) - - // If there is no current rate limit map or the rate limit map reference - // a different timestamp, reset limits. - if m.currGeneration == nil || m.currGeneration.truncTS != truncTS { - m.currGeneration = newLimitedKeys(truncTS) - } - - // Pull out the limiter so we can unlock before incrementing the limit. - limiter := m.currGeneration - - m.mtx.Unlock() - - return limiter.Take(key, m.max), nil -} - -// RedisFrontendRateLimiter is a rate limiter that stores data in Redis. -// It uses the basic rate limiter pattern described on the Redis best -// practices website: https://redis.com/redis-best-practices/basic-rate-limiting/. -type RedisFrontendRateLimiter struct { - r *redis.Client - dur time.Duration - max int - prefix string -} - -func NewRedisFrontendRateLimiter(r *redis.Client, dur time.Duration, max int, prefix string) FrontendRateLimiter { - return &RedisFrontendRateLimiter{ - r: r, - dur: dur, - max: max, - prefix: prefix, - } -} - -func (r *RedisFrontendRateLimiter) Take(ctx context.Context, key string) (bool, error) { - var incr *redis.IntCmd - truncTS := truncateNow(r.dur) - fullKey := fmt.Sprintf("rate_limit:%s:%s:%d", r.prefix, key, truncTS) - _, err := r.r.Pipelined(ctx, func(pipe redis.Pipeliner) error { - incr = pipe.Incr(ctx, fullKey) - pipe.PExpire(ctx, fullKey, r.dur-time.Millisecond) - return nil - }) - if err != nil { - frontendRateLimitTakeErrors.Inc() - return false, err - } - - return incr.Val()-1 < int64(r.max), nil -} - -type noopFrontendRateLimiter struct{} - -var NoopFrontendRateLimiter = &noopFrontendRateLimiter{} - -func (n *noopFrontendRateLimiter) Take(ctx context.Context, key string) (bool, error) { - return true, nil -} - -// truncateNow truncates the current timestamp -// to the specified duration. -func truncateNow(dur time.Duration) int64 { - return time.Now().Truncate(dur).Unix() -}
diff --git OP/proxyd/frontend_rate_limiter_test.go CELO/proxyd/frontend_rate_limiter_test.go deleted file mode 100644 index fb5f808bb5ecaf85727398968faa6df79b848d5d..0000000000000000000000000000000000000000 --- OP/proxyd/frontend_rate_limiter_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package proxyd - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/alicebob/miniredis" - "github.com/redis/go-redis/v9" - "github.com/stretchr/testify/require" -) - -func TestFrontendRateLimiter(t *testing.T) { - redisServer, err := miniredis.Run() - require.NoError(t, err) - defer redisServer.Close() - - redisClient := redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("127.0.0.1:%s", redisServer.Port()), - }) - - max := 2 - lims := []struct { - name string - frl FrontendRateLimiter - }{ - {"memory", NewMemoryFrontendRateLimit(2*time.Second, max)}, - {"redis", NewRedisFrontendRateLimiter(redisClient, 2*time.Second, max, "")}, - } - - for _, cfg := range lims { - frl := cfg.frl - ctx := context.Background() - t.Run(cfg.name, func(t *testing.T) { - for i := 0; i < 4; i++ { - ok, err := frl.Take(ctx, "foo") - require.NoError(t, err) - require.Equal(t, i < max, ok) - ok, err = frl.Take(ctx, "bar") - require.NoError(t, err) - require.Equal(t, i < max, ok) - } - time.Sleep(2 * time.Second) - for i := 0; i < 4; i++ { - ok, _ := frl.Take(ctx, "foo") - require.Equal(t, i < max, ok) - ok, _ = frl.Take(ctx, "bar") - require.Equal(t, i < max, ok) - } - }) - } -}
(deleted)
+0
-86
diff --git OP/proxyd/go.mod CELO/proxyd/go.mod deleted file mode 100644 index 088bf9bc9ed3e2dcd41ed5b4c4eae85881b01925..0000000000000000000000000000000000000000 --- OP/proxyd/go.mod +++ /dev/null @@ -1,86 +0,0 @@ -module github.com/ethereum-optimism/optimism/proxyd - -go 1.21 - -require ( - github.com/BurntSushi/toml v1.3.2 - github.com/alicebob/miniredis v2.5.0+incompatible - github.com/emirpasic/gods v1.18.1 - github.com/ethereum/go-ethereum v1.13.15 - github.com/go-redsync/redsync/v4 v4.10.0 - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb - github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.5.0 - github.com/hashicorp/golang-lru v1.0.2 - github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.17.0 - github.com/redis/go-redis/v9 v9.2.1 - github.com/rs/cors v1.10.1 - github.com/stretchr/testify v1.8.4 - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/xaionaro-go/weightedshuffle v0.0.0-20211213010739-6a74fbc7d24a - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - golang.org/x/sync v0.5.0 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - github.com/DataDog/zstd v1.5.5 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/VictoriaMetrics/fastcache v1.12.1 // indirect - github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.10.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cockroachdb/errors v1.11.1 // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v0.0.0-20231020221949-babd592d2360 // indirect - github.com/cockroachdb/redact v1.1.5 // indirect - github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.12.1 // indirect - github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect - github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deckarep/golang-set/v2 v2.3.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect - github.com/getsentry/sentry-go v0.25.0 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/gofrs/flock v0.8.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/gomodule/redigo v1.8.9 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.4 // indirect - github.com/klauspost/compress v1.17.1 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect - github.com/mmcloughlin/addchain v0.4.0 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/supranational/blst v0.3.11 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect - github.com/yuin/gopher-lua v1.1.0 // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.15.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect - rsc.io/tmplfunc v0.0.3 // indirect -)
(deleted)
+0
-290
diff --git OP/proxyd/go.sum CELO/proxyd/go.sum deleted file mode 100644 index 11a684f0e398d0d70ed4bd4827b86777e06bb271..0000000000000000000000000000000000000000 --- OP/proxyd/go.sum +++ /dev/null @@ -1,290 +0,0 @@ -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= -github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= -github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= -github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= -github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= -github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= -github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= -github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20231020221949-babd592d2360 h1:x1dzGu9e1FYmkG8mL9emtdWD1EzH/17SijnoLvKvPiM= -github.com/cockroachdb/pebble v0.0.0-20231020221949-babd592d2360/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= -github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= -github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= -github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= -github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= -github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= -github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= -github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A= -github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= -github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.13.15 h1:U7sSGYGo4SPjP6iNIifNoyIAiNjrmQkz6EwQG+/EZWo= -github.com/ethereum/go-ethereum v1.13.15/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= -github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= -github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= -github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= -github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= -github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= -github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= -github.com/go-redsync/redsync/v4 v4.10.0 h1:hTeAak4C73mNBQSTq6KCKDFaiIlfC+z5yTTl8fCJuBs= -github.com/go-redsync/redsync/v4 v4.10.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= -github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= -github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= -github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= -github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= -github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= -github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= -github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= -github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= -github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= -github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= -github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= -github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= -github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/xaionaro-go/weightedshuffle v0.0.0-20211213010739-6a74fbc7d24a h1:WS5nQycV+82Ndezq0UcMcGVG416PZgcJPqI/bLM824A= -github.com/xaionaro-go/weightedshuffle v0.0.0-20211213010739-6a74fbc7d24a/go.mod h1:0KAUfC65le2kMu4fnBxm7Xj3PkQ3MBpJbF5oMmqufBc= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= -github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
(deleted)
+0
-92
diff --git OP/proxyd/methods.go CELO/proxyd/methods.go deleted file mode 100644 index 08ea773288ab2fb08d25351db7efc8f8f0bd8169..0000000000000000000000000000000000000000 --- OP/proxyd/methods.go +++ /dev/null @@ -1,92 +0,0 @@ -package proxyd - -import ( - "context" - "crypto/sha256" - "encoding/json" - "fmt" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/log" -) - -type RPCMethodHandler interface { - GetRPCMethod(context.Context, *RPCReq) (*RPCRes, error) - PutRPCMethod(context.Context, *RPCReq, *RPCRes) error -} - -type StaticMethodHandler struct { - cache Cache - m sync.RWMutex - filterGet func(*RPCReq) bool - filterPut func(*RPCReq, *RPCRes) bool -} - -func (e *StaticMethodHandler) key(req *RPCReq) string { - // signature is the hashed json.RawMessage param contents - h := sha256.New() - h.Write(req.Params) - signature := fmt.Sprintf("%x", h.Sum(nil)) - return strings.Join([]string{"cache", req.Method, signature}, ":") -} - -func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - if e.cache == nil { - return nil, nil - } - if e.filterGet != nil && !e.filterGet(req) { - return nil, nil - } - - e.m.RLock() - defer e.m.RUnlock() - - key := e.key(req) - val, err := e.cache.Get(ctx, key) - if err != nil { - log.Error("error reading from cache", "key", key, "method", req.Method, "err", err) - return nil, err - } - if val == "" { - return nil, nil - } - - var result interface{} - if err := json.Unmarshal([]byte(val), &result); err != nil { - log.Error("error unmarshalling value from cache", "key", key, "method", req.Method, "err", err) - return nil, err - } - return &RPCRes{ - JSONRPC: req.JSONRPC, - Result: result, - ID: req.ID, - }, nil -} - -func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - if e.cache == nil { - return nil - } - // if there is a filter on get, we don't want to cache it because its irretrievable - if e.filterGet != nil && !e.filterGet(req) { - return nil - } - // response filter - if e.filterPut != nil && !e.filterPut(req, res) { - return nil - } - - e.m.Lock() - defer e.m.Unlock() - - key := e.key(req) - value := mustMarshalJSON(res.Result) - - err := e.cache.Put(ctx, key, string(value)) - if err != nil { - log.Error("error putting into cache", "key", key, "method", req.Method, "err", err) - return err - } - return nil -}
(deleted)
+0
-601
diff --git OP/proxyd/metrics.go CELO/proxyd/metrics.go deleted file mode 100644 index 4046af031c9f2da8515ee8a07ee7b60bdd226134..0000000000000000000000000000000000000000 --- OP/proxyd/metrics.go +++ /dev/null @@ -1,601 +0,0 @@ -package proxyd - -import ( - "context" - "fmt" - "regexp" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" -) - -const ( - MetricsNamespace = "proxyd" - - RPCRequestSourceHTTP = "http" - RPCRequestSourceWS = "ws" - - BackendProxyd = "proxyd" - SourceClient = "client" - SourceBackend = "backend" - MethodUnknown = "unknown" -) - -var PayloadSizeBuckets = []float64{10, 50, 100, 500, 1000, 5000, 10000, 100000, 1000000} -var MillisecondDurationBuckets = []float64{1, 10, 50, 100, 500, 1000, 5000, 10000, 100000} - -var ( - rpcRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "rpc_requests_total", - Help: "Count of total client RPC requests.", - }) - - rpcForwardsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "rpc_forwards_total", - Help: "Count of total RPC requests forwarded to each backend.", - }, []string{ - "auth", - "backend_name", - "method_name", - "source", - }) - - rpcBackendHTTPResponseCodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "rpc_backend_http_response_codes_total", - Help: "Count of total backend responses by HTTP status code.", - }, []string{ - "auth", - "backend_name", - "method_name", - "status_code", - "batched", - }) - - rpcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "rpc_errors_total", - Help: "Count of total RPC errors.", - }, []string{ - "auth", - "backend_name", - "method_name", - "error_code", - }) - - rpcSpecialErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "rpc_special_errors_total", - Help: "Count of total special RPC errors.", - }, []string{ - "auth", - "backend_name", - "method_name", - "error_type", - }) - - rpcBackendRequestDurationSumm = promauto.NewSummaryVec(prometheus.SummaryOpts{ - Namespace: MetricsNamespace, - Name: "rpc_backend_request_duration_seconds", - Help: "Summary of backend response times broken down by backend and method name.", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.95: 0.005, 0.99: 0.001}, - }, []string{ - "backend_name", - "method_name", - "batched", - }) - - activeClientWsConnsGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "active_client_ws_conns", - Help: "Gauge of active client WS connections.", - }, []string{ - "auth", - }) - - activeBackendWsConnsGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "active_backend_ws_conns", - Help: "Gauge of active backend WS connections.", - }, []string{ - "backend_name", - }) - - unserviceableRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "unserviceable_requests_total", - Help: "Count of total requests that were rejected due to no backends being available.", - }, []string{ - "auth", - "request_source", - }) - - httpResponseCodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "http_response_codes_total", - Help: "Count of total HTTP response codes.", - }, []string{ - "status_code", - }) - - httpRequestDurationSumm = promauto.NewSummary(prometheus.SummaryOpts{ - Namespace: MetricsNamespace, - Name: "http_request_duration_seconds", - Help: "Summary of HTTP request durations, in seconds.", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.95: 0.005, 0.99: 0.001}, - }) - - wsMessagesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "ws_messages_total", - Help: "Count of total websocket messages including protocol control.", - }, []string{ - "auth", - "backend_name", - "source", - }) - - redisErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "redis_errors_total", - Help: "Count of total Redis errors.", - }, []string{ - "source", - }) - - requestPayloadSizesGauge = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricsNamespace, - Name: "request_payload_sizes", - Help: "Histogram of client request payload sizes.", - Buckets: PayloadSizeBuckets, - }, []string{ - "auth", - }) - - responsePayloadSizesGauge = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricsNamespace, - Name: "response_payload_sizes", - Help: "Histogram of client response payload sizes.", - Buckets: PayloadSizeBuckets, - }, []string{ - "auth", - }) - - cacheHitsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "cache_hits_total", - Help: "Number of cache hits.", - }, []string{ - "method", - }) - - cacheMissesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "cache_misses_total", - Help: "Number of cache misses.", - }, []string{ - "method", - }) - - cacheErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "cache_errors_total", - Help: "Number of cache errors.", - }, []string{ - "method", - }) - - batchRPCShortCircuitsTotal = promauto.NewCounter(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "batch_rpc_short_circuits_total", - Help: "Count of total batch RPC short-circuits.", - }) - - rpcSpecialErrors = []string{ - "nonce too low", - "gas price too high", - "gas price too low", - "invalid parameters", - } - - redisCacheDurationSumm = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricsNamespace, - Name: "redis_cache_duration_milliseconds", - Help: "Histogram of Redis command durations, in milliseconds.", - Buckets: MillisecondDurationBuckets, - }, []string{"command"}) - - tooManyRequestErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "too_many_request_errors_total", - Help: "Count of request timeouts due to too many concurrent RPCs.", - }, []string{ - "backend_name", - }) - - batchSizeHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ - Namespace: MetricsNamespace, - Name: "batch_size_summary", - Help: "Summary of batch sizes", - Buckets: []float64{ - 1, - 5, - 10, - 25, - 50, - 100, - }, - }) - - frontendRateLimitTakeErrors = promauto.NewCounter(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "rate_limit_take_errors", - Help: "Count of errors taking frontend rate limits", - }) - - consensusLatestBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_latest_block", - Help: "Consensus latest block", - }, []string{ - "backend_group_name", - }) - - consensusSafeBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_safe_block", - Help: "Consensus safe block", - }, []string{ - "backend_group_name", - }) - - consensusFinalizedBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_finalized_block", - Help: "Consensus finalized block", - }, []string{ - "backend_group_name", - }) - - consensusHAError = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_ha_error", - Help: "Consensus HA error count", - }, []string{ - "error", - }) - - consensusHALatestBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_ha_latest_block", - Help: "Consensus HA latest block", - }, []string{ - "backend_group_name", - "leader", - }) - - consensusHASafeBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_ha_safe_block", - Help: "Consensus HA safe block", - }, []string{ - "backend_group_name", - "leader", - }) - - consensusHAFinalizedBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_ha_finalized_block", - Help: "Consensus HA finalized block", - }, []string{ - "backend_group_name", - "leader", - }) - - backendLatestBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_latest_block", - Help: "Current latest block observed per backend", - }, []string{ - "backend_name", - }) - - backendSafeBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_safe_block", - Help: "Current safe block observed per backend", - }, []string{ - "backend_name", - }) - - backendFinalizedBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_finalized_block", - Help: "Current finalized block observed per backend", - }, []string{ - "backend_name", - }) - - backendUnexpectedBlockTagsBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_unexpected_block_tags", - Help: "Bool gauge for unexpected block tags", - }, []string{ - "backend_name", - }) - - consensusGroupCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_count", - Help: "Consensus group serving traffic count", - }, []string{ - "backend_group_name", - }) - - consensusGroupFilteredCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_filtered_count", - Help: "Consensus group filtered out from serving traffic count", - }, []string{ - "backend_group_name", - }) - - consensusGroupTotalCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "group_consensus_total_count", - Help: "Total count of candidates to be part of consensus group", - }, []string{ - "backend_group_name", - }) - - consensusBannedBackends = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "consensus_backend_banned", - Help: "Bool gauge for banned backends", - }, []string{ - "backend_name", - }) - - consensusPeerCountBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "consensus_backend_peer_count", - Help: "Peer count", - }, []string{ - "backend_name", - }) - - consensusInSyncBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "consensus_backend_in_sync", - Help: "Bool gauge for backends in sync", - }, []string{ - "backend_name", - }) - - consensusUpdateDelayBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "consensus_backend_update_delay", - Help: "Delay (ms) for backend update", - }, []string{ - "backend_name", - }) - - avgLatencyBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_avg_latency", - Help: "Average latency per backend", - }, []string{ - "backend_name", - }) - - degradedBackends = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_degraded", - Help: "Bool gauge for degraded backends", - }, []string{ - "backend_name", - }) - - networkErrorRateBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_error_rate", - Help: "Request error rate per backend", - }, []string{ - "backend_name", - }) - - healthyPrimaryCandidates = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "healthy_candidates", - Help: "Record the number of healthy primary candidates", - }, []string{ - "backend_group_name", - }) - - backendGroupFallbackBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricsNamespace, - Name: "backend_group_fallback_backenend", - Help: "Bool gauge for if a backend is a fallback for a backend group", - }, []string{ - "backend_group", - "backend_name", - "fallback", - }) -) - -func RecordRedisError(source string) { - redisErrorsTotal.WithLabelValues(source).Inc() -} - -func RecordRPCError(ctx context.Context, backendName, method string, err error) { - rpcErr, ok := err.(*RPCErr) - var code int - if ok { - MaybeRecordSpecialRPCError(ctx, backendName, method, rpcErr) - code = rpcErr.Code - } else { - code = -1 - } - - rpcErrorsTotal.WithLabelValues(GetAuthCtx(ctx), backendName, method, strconv.Itoa(code)).Inc() -} - -func RecordWSMessage(ctx context.Context, backendName, source string) { - wsMessagesTotal.WithLabelValues(GetAuthCtx(ctx), backendName, source).Inc() -} - -func RecordUnserviceableRequest(ctx context.Context, source string) { - unserviceableRequestsTotal.WithLabelValues(GetAuthCtx(ctx), source).Inc() -} - -func RecordRPCForward(ctx context.Context, backendName, method, source string) { - rpcForwardsTotal.WithLabelValues(GetAuthCtx(ctx), backendName, method, source).Inc() -} - -func MaybeRecordSpecialRPCError(ctx context.Context, backendName, method string, rpcErr *RPCErr) { - errMsg := strings.ToLower(rpcErr.Message) - for _, errStr := range rpcSpecialErrors { - if strings.Contains(errMsg, errStr) { - rpcSpecialErrorsTotal.WithLabelValues(GetAuthCtx(ctx), backendName, method, errStr).Inc() - return - } - } -} - -func RecordRequestPayloadSize(ctx context.Context, payloadSize int) { - requestPayloadSizesGauge.WithLabelValues(GetAuthCtx(ctx)).Observe(float64(payloadSize)) -} - -func RecordResponsePayloadSize(ctx context.Context, payloadSize int) { - responsePayloadSizesGauge.WithLabelValues(GetAuthCtx(ctx)).Observe(float64(payloadSize)) -} - -func RecordCacheHit(method string) { - cacheHitsTotal.WithLabelValues(method).Inc() -} - -func RecordCacheMiss(method string) { - cacheMissesTotal.WithLabelValues(method).Inc() -} - -func RecordCacheError(method string) { - cacheErrorsTotal.WithLabelValues(method).Inc() -} - -func RecordBatchSize(size int) { - batchSizeHistogram.Observe(float64(size)) -} - -var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z ]+`) - -func RecordGroupConsensusError(group *BackendGroup, label string, err error) { - errClean := nonAlphanumericRegex.ReplaceAllString(err.Error(), "") - errClean = strings.ReplaceAll(errClean, " ", "_") - errClean = strings.ReplaceAll(errClean, "__", "_") - label = fmt.Sprintf("%s.%s", label, errClean) - consensusHAError.WithLabelValues(label).Inc() -} - -func RecordGroupConsensusHALatestBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { - consensusHALatestBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) -} - -func RecordGroupConsensusHASafeBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { - consensusHASafeBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) -} - -func RecordGroupConsensusHAFinalizedBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { - consensusHAFinalizedBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) -} - -func RecordGroupConsensusLatestBlock(group *BackendGroup, blockNumber hexutil.Uint64) { - consensusLatestBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) -} - -func RecordGroupConsensusSafeBlock(group *BackendGroup, blockNumber hexutil.Uint64) { - consensusSafeBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) -} - -func RecordGroupConsensusFinalizedBlock(group *BackendGroup, blockNumber hexutil.Uint64) { - consensusFinalizedBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) -} - -func RecordGroupConsensusCount(group *BackendGroup, count int) { - consensusGroupCount.WithLabelValues(group.Name).Set(float64(count)) -} - -func RecordGroupConsensusFilteredCount(group *BackendGroup, count int) { - consensusGroupFilteredCount.WithLabelValues(group.Name).Set(float64(count)) -} - -func RecordGroupTotalCount(group *BackendGroup, count int) { - consensusGroupTotalCount.WithLabelValues(group.Name).Set(float64(count)) -} - -func RecordBackendLatestBlock(b *Backend, blockNumber hexutil.Uint64) { - backendLatestBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) -} - -func RecordBackendSafeBlock(b *Backend, blockNumber hexutil.Uint64) { - backendSafeBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) -} - -func RecordBackendFinalizedBlock(b *Backend, blockNumber hexutil.Uint64) { - backendFinalizedBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) -} - -func RecordBackendUnexpectedBlockTags(b *Backend, unexpected bool) { - backendUnexpectedBlockTagsBackend.WithLabelValues(b.Name).Set(boolToFloat64(unexpected)) -} - -func RecordConsensusBackendBanned(b *Backend, banned bool) { - consensusBannedBackends.WithLabelValues(b.Name).Set(boolToFloat64(banned)) -} - -func RecordHealthyCandidates(b *BackendGroup, candidates int) { - healthyPrimaryCandidates.WithLabelValues(b.Name).Set(float64(candidates)) -} - -func RecordConsensusBackendPeerCount(b *Backend, peerCount uint64) { - consensusPeerCountBackend.WithLabelValues(b.Name).Set(float64(peerCount)) -} - -func RecordConsensusBackendInSync(b *Backend, inSync bool) { - consensusInSyncBackend.WithLabelValues(b.Name).Set(boolToFloat64(inSync)) -} - -func RecordConsensusBackendUpdateDelay(b *Backend, lastUpdate time.Time) { - // avoid recording the delay for the first update - if lastUpdate.IsZero() { - return - } - delay := time.Since(lastUpdate) - consensusUpdateDelayBackend.WithLabelValues(b.Name).Set(float64(delay.Milliseconds())) -} - -func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency time.Duration) { - avgLatencyBackend.WithLabelValues(b.Name).Set(float64(avgLatency.Milliseconds())) - degradedBackends.WithLabelValues(b.Name).Set(boolToFloat64(b.IsDegraded())) -} - -func RecordBackendNetworkErrorRateSlidingWindow(b *Backend, rate float64) { - networkErrorRateBackend.WithLabelValues(b.Name).Set(rate) -} - -func RecordBackendGroupFallbacks(bg *BackendGroup, name string, fallback bool) { - backendGroupFallbackBackend.WithLabelValues(bg.Name, name, strconv.FormatBool(fallback)).Set(boolToFloat64(fallback)) -} - -func boolToFloat64(b bool) float64 { - if b { - return 1 - } - return 0 -}
(deleted)
+0
-472
diff --git OP/proxyd/proxyd.go CELO/proxyd/proxyd.go deleted file mode 100644 index 402909b5f40430e253f645d0a223f2ad496b2df4..0000000000000000000000000000000000000000 --- OP/proxyd/proxyd.go +++ /dev/null @@ -1,472 +0,0 @@ -package proxyd - -import ( - "context" - "crypto/tls" - "errors" - "fmt" - "net/http" - "os" - "time" - - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/log" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/redis/go-redis/v9" - "golang.org/x/exp/slog" - "golang.org/x/sync/semaphore" -) - -func SetLogLevel(logLevel slog.Leveler) { - log.SetDefault(log.NewLogger(slog.NewJSONHandler( - os.Stdout, &slog.HandlerOptions{Level: logLevel}))) -} - -func Start(config *Config) (*Server, func(), error) { - if len(config.Backends) == 0 { - return nil, nil, errors.New("must define at least one backend") - } - if len(config.BackendGroups) == 0 { - return nil, nil, errors.New("must define at least one backend group") - } - if len(config.RPCMethodMappings) == 0 { - return nil, nil, errors.New("must define at least one RPC method mapping") - } - - for authKey := range config.Authentication { - if authKey == "none" { - return nil, nil, errors.New("cannot use none as an auth key") - } - } - - var redisClient *redis.Client - if config.Redis.URL != "" { - rURL, err := ReadFromEnvOrConfig(config.Redis.URL) - if err != nil { - return nil, nil, err - } - redisClient, err = NewRedisClient(rURL) - if err != nil { - return nil, nil, err - } - } - - if redisClient == nil && config.RateLimit.UseRedis { - return nil, nil, errors.New("must specify a Redis URL if UseRedis is true in rate limit config") - } - - // While modifying shared globals is a bad practice, the alternative - // is to clone these errors on every invocation. This is inefficient. - // We'd also have to make sure that errors.Is and errors.As continue - // to function properly on the cloned errors. - if config.RateLimit.ErrorMessage != "" { - ErrOverRateLimit.Message = config.RateLimit.ErrorMessage - } - if config.WhitelistErrorMessage != "" { - ErrMethodNotWhitelisted.Message = config.WhitelistErrorMessage - } - if config.BatchConfig.ErrorMessage != "" { - ErrTooManyBatchRequests.Message = config.BatchConfig.ErrorMessage - } - - if config.SenderRateLimit.Enabled { - if config.SenderRateLimit.Limit <= 0 { - return nil, nil, errors.New("limit in sender_rate_limit must be > 0") - } - if time.Duration(config.SenderRateLimit.Interval) < time.Second { - return nil, nil, errors.New("interval in sender_rate_limit must be >= 1s") - } - } - - maxConcurrentRPCs := config.Server.MaxConcurrentRPCs - if maxConcurrentRPCs == 0 { - maxConcurrentRPCs = math.MaxInt64 - } - rpcRequestSemaphore := semaphore.NewWeighted(maxConcurrentRPCs) - - backendNames := make([]string, 0) - backendsByName := make(map[string]*Backend) - for name, cfg := range config.Backends { - opts := make([]BackendOpt, 0) - - rpcURL, err := ReadFromEnvOrConfig(cfg.RPCURL) - if err != nil { - return nil, nil, err - } - wsURL, err := ReadFromEnvOrConfig(cfg.WSURL) - if err != nil { - return nil, nil, err - } - if rpcURL == "" { - return nil, nil, fmt.Errorf("must define an RPC URL for backend %s", name) - } - - if config.BackendOptions.ResponseTimeoutSeconds != 0 { - timeout := secondsToDuration(config.BackendOptions.ResponseTimeoutSeconds) - opts = append(opts, WithTimeout(timeout)) - } - if config.BackendOptions.MaxRetries != 0 { - opts = append(opts, WithMaxRetries(config.BackendOptions.MaxRetries)) - } - if config.BackendOptions.MaxResponseSizeBytes != 0 { - opts = append(opts, WithMaxResponseSize(config.BackendOptions.MaxResponseSizeBytes)) - } - if config.BackendOptions.OutOfServiceSeconds != 0 { - opts = append(opts, WithOutOfServiceDuration(secondsToDuration(config.BackendOptions.OutOfServiceSeconds))) - } - if config.BackendOptions.MaxDegradedLatencyThreshold > 0 { - opts = append(opts, WithMaxDegradedLatencyThreshold(time.Duration(config.BackendOptions.MaxDegradedLatencyThreshold))) - } - if config.BackendOptions.MaxLatencyThreshold > 0 { - opts = append(opts, WithMaxLatencyThreshold(time.Duration(config.BackendOptions.MaxLatencyThreshold))) - } - if config.BackendOptions.MaxErrorRateThreshold > 0 { - opts = append(opts, WithMaxErrorRateThreshold(config.BackendOptions.MaxErrorRateThreshold)) - } - if cfg.MaxRPS != 0 { - opts = append(opts, WithMaxRPS(cfg.MaxRPS)) - } - if cfg.MaxWSConns != 0 { - opts = append(opts, WithMaxWSConns(cfg.MaxWSConns)) - } - if cfg.Password != "" { - passwordVal, err := ReadFromEnvOrConfig(cfg.Password) - if err != nil { - return nil, nil, err - } - opts = append(opts, WithBasicAuth(cfg.Username, passwordVal)) - } - - headers := map[string]string{} - for headerName, headerValue := range cfg.Headers { - headerValue, err := ReadFromEnvOrConfig(headerValue) - if err != nil { - return nil, nil, err - } - - headers[headerName] = headerValue - } - opts = append(opts, WithHeaders(headers)) - - tlsConfig, err := configureBackendTLS(cfg) - if err != nil { - return nil, nil, err - } - if tlsConfig != nil { - log.Info("using custom TLS config for backend", "name", name) - opts = append(opts, WithTLSConfig(tlsConfig)) - } - if cfg.StripTrailingXFF { - opts = append(opts, WithStrippedTrailingXFF()) - } - opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) - opts = append(opts, WithConsensusSkipPeerCountCheck(cfg.ConsensusSkipPeerCountCheck)) - opts = append(opts, WithConsensusForcedCandidate(cfg.ConsensusForcedCandidate)) - opts = append(opts, WithWeight(cfg.Weight)) - - receiptsTarget, err := ReadFromEnvOrConfig(cfg.ConsensusReceiptsTarget) - if err != nil { - return nil, nil, err - } - receiptsTarget, err = validateReceiptsTarget(receiptsTarget) - if err != nil { - return nil, nil, err - } - opts = append(opts, WithConsensusReceiptTarget(receiptsTarget)) - - back := NewBackend(name, rpcURL, wsURL, rpcRequestSemaphore, opts...) - backendNames = append(backendNames, name) - backendsByName[name] = back - log.Info("configured backend", - "name", name, - "backend_names", backendNames, - "rpc_url", rpcURL, - "ws_url", wsURL) - } - - backendGroups := make(map[string]*BackendGroup) - for bgName, bg := range config.BackendGroups { - backends := make([]*Backend, 0) - fallbackBackends := make(map[string]bool) - fallbackCount := 0 - for _, bName := range bg.Backends { - if backendsByName[bName] == nil { - return nil, nil, fmt.Errorf("backend %s is not defined", bName) - } - backends = append(backends, backendsByName[bName]) - - for _, fb := range bg.Fallbacks { - if bName == fb { - fallbackBackends[bName] = true - log.Info("configured backend as fallback", - "backend_name", bName, - "backend_group", bgName, - ) - fallbackCount++ - } - } - - if _, ok := fallbackBackends[bName]; !ok { - fallbackBackends[bName] = false - log.Info("configured backend as primary", - "backend_name", bName, - "backend_group", bgName, - ) - } - } - - if fallbackCount != len(bg.Fallbacks) { - return nil, nil, - fmt.Errorf( - "error: number of fallbacks instantiated (%d) did not match configured (%d) for backend group %s", - fallbackCount, len(bg.Fallbacks), bgName, - ) - } - - backendGroups[bgName] = &BackendGroup{ - Name: bgName, - Backends: backends, - WeightedRouting: bg.WeightedRouting, - FallbackBackends: fallbackBackends, - } - } - - var wsBackendGroup *BackendGroup - if config.WSBackendGroup != "" { - wsBackendGroup = backendGroups[config.WSBackendGroup] - if wsBackendGroup == nil { - return nil, nil, fmt.Errorf("ws backend group %s does not exist", config.WSBackendGroup) - } - } - - if wsBackendGroup == nil && config.Server.WSPort != 0 { - return nil, nil, fmt.Errorf("a ws port was defined, but no ws group was defined") - } - - for _, bg := range config.RPCMethodMappings { - if backendGroups[bg] == nil { - return nil, nil, fmt.Errorf("undefined backend group %s", bg) - } - } - - var resolvedAuth map[string]string - - if config.Authentication != nil { - resolvedAuth = make(map[string]string) - for secret, alias := range config.Authentication { - resolvedSecret, err := ReadFromEnvOrConfig(secret) - if err != nil { - return nil, nil, err - } - resolvedAuth[resolvedSecret] = alias - } - } - - var ( - cache Cache - rpcCache RPCCache - ) - if config.Cache.Enabled { - if redisClient == nil { - log.Warn("redis is not configured, using in-memory cache") - cache = newMemoryCache() - } else { - ttl := defaultCacheTtl - if config.Cache.TTL != 0 { - ttl = time.Duration(config.Cache.TTL) - } - cache = newRedisCache(redisClient, config.Redis.Namespace, ttl) - } - rpcCache = newRPCCache(newCacheWithCompression(cache)) - } - - srv, err := NewServer( - backendGroups, - wsBackendGroup, - NewStringSetFromStrings(config.WSMethodWhitelist), - config.RPCMethodMappings, - config.Server.MaxBodySizeBytes, - resolvedAuth, - secondsToDuration(config.Server.TimeoutSeconds), - config.Server.MaxUpstreamBatchSize, - config.Server.EnableXServedByHeader, - rpcCache, - config.RateLimit, - config.SenderRateLimit, - config.Server.EnableRequestLog, - config.Server.MaxRequestBodyLogLen, - config.BatchConfig.MaxSize, - redisClient, - ) - if err != nil { - return nil, nil, fmt.Errorf("error creating server: %w", err) - } - - // Enable to support browser websocket connections. - // See https://pkg.go.dev/github.com/gorilla/websocket#hdr-Origin_Considerations - if config.Server.AllowAllOrigins { - srv.upgrader.CheckOrigin = func(r *http.Request) bool { - return true - } - } - - if config.Metrics.Enabled { - addr := fmt.Sprintf("%s:%d", config.Metrics.Host, config.Metrics.Port) - log.Info("starting metrics server", "addr", addr) - go func() { - if err := http.ListenAndServe(addr, promhttp.Handler()); err != nil { - log.Error("error starting metrics server", "err", err) - } - }() - } - - // To allow integration tests to cleanly come up, wait - // 10ms to give the below goroutines enough time to - // encounter an error creating their servers - errTimer := time.NewTimer(10 * time.Millisecond) - - if config.Server.RPCPort != 0 { - go func() { - if err := srv.RPCListenAndServe(config.Server.RPCHost, config.Server.RPCPort); err != nil { - if errors.Is(err, http.ErrServerClosed) { - log.Info("RPC server shut down") - return - } - log.Crit("error starting RPC server", "err", err) - } - }() - } - - if config.Server.WSPort != 0 { - go func() { - if err := srv.WSListenAndServe(config.Server.WSHost, config.Server.WSPort); err != nil { - if errors.Is(err, http.ErrServerClosed) { - log.Info("WS server shut down") - return - } - log.Crit("error starting WS server", "err", err) - } - }() - } else { - log.Info("WS server not enabled (ws_port is set to 0)") - } - - for bgName, bg := range backendGroups { - bgcfg := config.BackendGroups[bgName] - if bgcfg.ConsensusAware { - log.Info("creating poller for consensus aware backend_group", "name", bgName) - - copts := make([]ConsensusOpt, 0) - - if bgcfg.ConsensusAsyncHandler == "noop" { - copts = append(copts, WithAsyncHandler(NewNoopAsyncHandler())) - } - if bgcfg.ConsensusBanPeriod > 0 { - copts = append(copts, WithBanPeriod(time.Duration(bgcfg.ConsensusBanPeriod))) - } - if bgcfg.ConsensusMaxUpdateThreshold > 0 { - copts = append(copts, WithMaxUpdateThreshold(time.Duration(bgcfg.ConsensusMaxUpdateThreshold))) - } - if bgcfg.ConsensusMaxBlockLag > 0 { - copts = append(copts, WithMaxBlockLag(bgcfg.ConsensusMaxBlockLag)) - } - if bgcfg.ConsensusMinPeerCount > 0 { - copts = append(copts, WithMinPeerCount(uint64(bgcfg.ConsensusMinPeerCount))) - } - if bgcfg.ConsensusMaxBlockRange > 0 { - copts = append(copts, WithMaxBlockRange(bgcfg.ConsensusMaxBlockRange)) - } - if bgcfg.ConsensusPollerInterval > 0 { - copts = append(copts, WithPollerInterval(time.Duration(bgcfg.ConsensusPollerInterval))) - } - - for _, be := range bgcfg.Backends { - if fallback, ok := bg.FallbackBackends[be]; !ok { - log.Crit("error backend not found in backend fallback configurations", "backend_name", be) - } else { - log.Debug("configuring new backend for group", "backend_group", bgName, "backend_name", be, "fallback", fallback) - RecordBackendGroupFallbacks(bg, be, fallback) - } - } - - var tracker ConsensusTracker - if bgcfg.ConsensusHA { - if bgcfg.ConsensusHARedis.URL == "" { - log.Crit("must specify a consensus_ha_redis config when consensus_ha is true") - } - topts := make([]RedisConsensusTrackerOpt, 0) - if bgcfg.ConsensusHALockPeriod > 0 { - topts = append(topts, WithLockPeriod(time.Duration(bgcfg.ConsensusHALockPeriod))) - } - if bgcfg.ConsensusHAHeartbeatInterval > 0 { - topts = append(topts, WithHeartbeatInterval(time.Duration(bgcfg.ConsensusHAHeartbeatInterval))) - } - consensusHARedisClient, err := NewRedisClient(bgcfg.ConsensusHARedis.URL) - if err != nil { - return nil, nil, err - } - ns := fmt.Sprintf("%s:%s", bgcfg.ConsensusHARedis.Namespace, bg.Name) - tracker = NewRedisConsensusTracker(context.Background(), consensusHARedisClient, bg, ns, topts...) - copts = append(copts, WithTracker(tracker)) - } - - cp := NewConsensusPoller(bg, copts...) - bg.Consensus = cp - - if bgcfg.ConsensusHA { - tracker.(*RedisConsensusTracker).Init() - } - } - } - - <-errTimer.C - log.Info("started proxyd") - - shutdownFunc := func() { - log.Info("shutting down proxyd") - srv.Shutdown() - log.Info("goodbye") - } - - return srv, shutdownFunc, nil -} - -func validateReceiptsTarget(val string) (string, error) { - if val == "" { - val = ReceiptsTargetDebugGetRawReceipts - } - switch val { - case ReceiptsTargetDebugGetRawReceipts, - ReceiptsTargetAlchemyGetTransactionReceipts, - ReceiptsTargetEthGetTransactionReceipts, - ReceiptsTargetParityGetTransactionReceipts: - return val, nil - default: - return "", fmt.Errorf("invalid receipts target: %s", val) - } -} - -func secondsToDuration(seconds int) time.Duration { - return time.Duration(seconds) * time.Second -} - -func configureBackendTLS(cfg *BackendConfig) (*tls.Config, error) { - if cfg.CAFile == "" { - return nil, nil - } - - tlsConfig, err := CreateTLSClient(cfg.CAFile) - if err != nil { - return nil, err - } - - if cfg.ClientCertFile != "" && cfg.ClientKeyFile != "" { - cert, err := ParseKeyPair(cfg.ClientCertFile, cfg.ClientKeyFile) - if err != nil { - return nil, err - } - tlsConfig.Certificates = []tls.Certificate{cert} - } - - return tlsConfig, nil -}
(deleted)
+0
-32
diff --git OP/proxyd/reader.go CELO/proxyd/reader.go deleted file mode 100644 index b16301f1f0870494e824e874dfa91fe6a9d320d3..0000000000000000000000000000000000000000 --- OP/proxyd/reader.go +++ /dev/null @@ -1,32 +0,0 @@ -package proxyd - -import ( - "errors" - "io" -) - -var ErrLimitReaderOverLimit = errors.New("over read limit") - -func LimitReader(r io.Reader, n int64) io.Reader { return &LimitedReader{r, n} } - -// A LimitedReader reads from R but limits the amount of -// data returned to just N bytes. Each call to Read -// updates N to reflect the new amount remaining. -// Unlike the standard library version, Read returns -// ErrLimitReaderOverLimit when N <= 0. -type LimitedReader struct { - R io.Reader // underlying reader - N int64 // max bytes remaining -} - -func (l *LimitedReader) Read(p []byte) (int, error) { - if l.N <= 0 { - return 0, ErrLimitReaderOverLimit - } - if int64(len(p)) > l.N { - p = p[0:l.N] - } - n, err := l.R.Read(p) - l.N -= int64(n) - return n, err -}
diff --git OP/proxyd/reader_test.go CELO/proxyd/reader_test.go deleted file mode 100644 index 2ee23456edfc1d1ced04ea7dde0018063d679f48..0000000000000000000000000000000000000000 --- OP/proxyd/reader_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package proxyd - -import ( - "github.com/stretchr/testify/require" - "io" - "strings" - "testing" -) - -func TestLimitReader(t *testing.T) { - data := "hellohellohellohello" - r := LimitReader(strings.NewReader(data), 3) - buf := make([]byte, 3) - - // Buffer reads OK - n, err := r.Read(buf) - require.NoError(t, err) - require.Equal(t, 3, n) - - // Buffer is over limit - n, err = r.Read(buf) - require.Equal(t, ErrLimitReaderOverLimit, err) - require.Equal(t, 0, n) - - // Buffer on initial read is over size - buf = make([]byte, 16) - r = LimitReader(strings.NewReader(data), 3) - n, err = r.Read(buf) - require.NoError(t, err) - require.Equal(t, 3, n) - - // test with read all where the limit is less than the data - r = LimitReader(strings.NewReader(data), 3) - out, err := io.ReadAll(r) - require.Equal(t, ErrLimitReaderOverLimit, err) - require.Equal(t, "hel", string(out)) - - // test with read all where the limit is more than the data - r = LimitReader(strings.NewReader(data), 21) - out, err = io.ReadAll(r) - require.NoError(t, err) - require.Equal(t, data, string(out)) -}
(deleted)
+0
-22
diff --git OP/proxyd/redis.go CELO/proxyd/redis.go deleted file mode 100644 index bd15f527f9b7d87ee5a6a433ba9f6dfac257a4a0..0000000000000000000000000000000000000000 --- OP/proxyd/redis.go +++ /dev/null @@ -1,22 +0,0 @@ -package proxyd - -import ( - "context" - "time" - - "github.com/redis/go-redis/v9" -) - -func NewRedisClient(url string) (*redis.Client, error) { - opts, err := redis.ParseURL(url) - if err != nil { - return nil, err - } - client := redis.NewClient(opts) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := client.Ping(ctx).Err(); err != nil { - return nil, wrapErr(err, "error connecting to redis") - } - return client, nil -}
(deleted)
+0
-310
diff --git OP/proxyd/rewriter.go CELO/proxyd/rewriter.go deleted file mode 100644 index 605787eff3128121269f3d072e601503cd728eba..0000000000000000000000000000000000000000 --- OP/proxyd/rewriter.go +++ /dev/null @@ -1,310 +0,0 @@ -package proxyd - -import ( - "encoding/json" - "errors" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rpc" -) - -type RewriteContext struct { - latest hexutil.Uint64 - safe hexutil.Uint64 - finalized hexutil.Uint64 - maxBlockRange uint64 -} - -type RewriteResult uint8 - -const ( - // RewriteNone means request should be forwarded as-is - RewriteNone RewriteResult = iota - - // RewriteOverrideError means there was an error attempting to rewrite - RewriteOverrideError - - // RewriteOverrideRequest means the modified request should be forwarded to the backend - RewriteOverrideRequest - - // RewriteOverrideResponse means to skip calling the backend and serve the overridden response - RewriteOverrideResponse -) - -var ( - ErrRewriteBlockOutOfRange = errors.New("block is out of range") - ErrRewriteRangeTooLarge = errors.New("block range is too large") -) - -// RewriteTags modifies the request and the response based on block tags -func RewriteTags(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { - rw, err := RewriteResponse(rctx, req, res) - if rw == RewriteOverrideResponse { - return rw, err - } - return RewriteRequest(rctx, req, res) -} - -// RewriteResponse modifies the response object to comply with the rewrite context -// after the method has been called at the backend -// RewriteResult informs the decision of the rewrite -func RewriteResponse(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { - switch req.Method { - case "eth_blockNumber": - res.Result = rctx.latest - return RewriteOverrideResponse, nil - } - return RewriteNone, nil -} - -// RewriteRequest modifies the request object to comply with the rewrite context -// before the method has been called at the backend -// it returns false if nothing was changed -func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { - switch req.Method { - case "eth_getLogs", - "eth_newFilter": - return rewriteRange(rctx, req, res, 0) - case "debug_getRawReceipts", "consensus_getReceipts": - return rewriteParam(rctx, req, res, 0, true, false) - case "eth_getBalance", - "eth_getCode", - "eth_getTransactionCount", - "eth_call": - return rewriteParam(rctx, req, res, 1, false, true) - case "eth_getStorageAt", - "eth_getProof": - return rewriteParam(rctx, req, res, 2, false, true) - case "eth_getBlockTransactionCountByNumber", - "eth_getUncleCountByBlockNumber", - "eth_getBlockByNumber", - "eth_getTransactionByBlockNumberAndIndex", - "eth_getUncleByBlockNumberAndIndex": - return rewriteParam(rctx, req, res, 0, false, false) - } - return RewriteNone, nil -} - -func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool, blockNrOrHash bool) (RewriteResult, error) { - var p []interface{} - err := json.Unmarshal(req.Params, &p) - if err != nil { - return RewriteOverrideError, err - } - - // we assume latest if the param is missing, - // and we don't rewrite if there is not enough params - if len(p) == pos && !required { - p = append(p, "latest") - } else if len(p) <= pos { - return RewriteNone, nil - } - - // support for https://eips.ethereum.org/EIPS/eip-1898 - var val interface{} - var rw bool - if blockNrOrHash { - bnh, err := remarshalBlockNumberOrHash(p[pos]) - if err != nil { - // fallback to string - s, ok := p[pos].(string) - if ok { - val, rw, err = rewriteTag(rctx, s) - if err != nil { - return RewriteOverrideError, err - } - } else { - return RewriteOverrideError, errors.New("expected BlockNumberOrHash or string") - } - } else { - val, rw, err = rewriteTagBlockNumberOrHash(rctx, bnh) - if err != nil { - return RewriteOverrideError, err - } - } - } else { - s, ok := p[pos].(string) - if !ok { - return RewriteOverrideError, errors.New("expected string") - } - - val, rw, err = rewriteTag(rctx, s) - if err != nil { - return RewriteOverrideError, err - } - } - - if rw { - p[pos] = val - paramRaw, err := json.Marshal(p) - if err != nil { - return RewriteOverrideError, err - } - req.Params = paramRaw - return RewriteOverrideRequest, nil - } - return RewriteNone, nil -} - -func rewriteRange(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (RewriteResult, error) { - var p []map[string]interface{} - err := json.Unmarshal(req.Params, &p) - if err != nil { - return RewriteOverrideError, err - } - - // if either fromBlock or toBlock is defined, default the other to "latest" if unset - _, hasFrom := p[pos]["fromBlock"] - _, hasTo := p[pos]["toBlock"] - if hasFrom && !hasTo { - p[pos]["toBlock"] = "latest" - } else if hasTo && !hasFrom { - p[pos]["fromBlock"] = "latest" - } - - modifiedFrom, err := rewriteTagMap(rctx, p[pos], "fromBlock") - if err != nil { - return RewriteOverrideError, err - } - - modifiedTo, err := rewriteTagMap(rctx, p[pos], "toBlock") - if err != nil { - return RewriteOverrideError, err - } - - if rctx.maxBlockRange > 0 && (hasFrom || hasTo) { - from, err := blockNumber(p[pos], "fromBlock", uint64(rctx.latest)) - if err != nil { - return RewriteOverrideError, err - } - to, err := blockNumber(p[pos], "toBlock", uint64(rctx.latest)) - if err != nil { - return RewriteOverrideError, err - } - if to-from > rctx.maxBlockRange { - return RewriteOverrideError, ErrRewriteRangeTooLarge - } - } - - // if any of the fields the request have been changed, re-marshal the params - if modifiedFrom || modifiedTo { - paramsRaw, err := json.Marshal(p) - req.Params = paramsRaw - if err != nil { - return RewriteOverrideError, err - } - return RewriteOverrideRequest, nil - } - - return RewriteNone, nil -} - -func blockNumber(m map[string]interface{}, key string, latest uint64) (uint64, error) { - current, ok := m[key].(string) - if !ok { - return 0, errors.New("expected string") - } - // the latest/safe/finalized tags are already replaced by rewriteTag - if current == "earliest" { - return 0, nil - } - if current == "pending" { - return latest + 1, nil - } - return hexutil.DecodeUint64(current) -} - -func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (bool, error) { - if m[key] == nil || m[key] == "" { - return false, nil - } - - current, ok := m[key].(string) - if !ok { - return false, errors.New("expected string") - } - - val, rw, err := rewriteTag(rctx, current) - if err != nil { - return false, err - } - if rw { - m[key] = val - return true, nil - } - - return false, nil -} - -func remarshalBlockNumberOrHash(current interface{}) (*rpc.BlockNumberOrHash, error) { - jv, err := json.Marshal(current) - if err != nil { - return nil, err - } - - var bnh rpc.BlockNumberOrHash - err = bnh.UnmarshalJSON(jv) - if err != nil { - return nil, err - } - - return &bnh, nil -} - -func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { - bnh, err := remarshalBlockNumberOrHash(current) - if err != nil { - return "", false, err - } - - // this is a hash, not a block - if bnh.BlockNumber == nil { - return current, false, nil - } - - switch *bnh.BlockNumber { - case rpc.PendingBlockNumber, - rpc.EarliestBlockNumber: - return current, false, nil - case rpc.FinalizedBlockNumber: - return rctx.finalized.String(), true, nil - case rpc.SafeBlockNumber: - return rctx.safe.String(), true, nil - case rpc.LatestBlockNumber: - return rctx.latest.String(), true, nil - default: - if bnh.BlockNumber.Int64() > int64(rctx.latest) { - return "", false, ErrRewriteBlockOutOfRange - } - } - - return current, false, nil -} - -func rewriteTagBlockNumberOrHash(rctx RewriteContext, current *rpc.BlockNumberOrHash) (*rpc.BlockNumberOrHash, bool, error) { - // this is a hash, not a block number - if current.BlockNumber == nil { - return current, false, nil - } - - switch *current.BlockNumber { - case rpc.PendingBlockNumber, - rpc.EarliestBlockNumber: - return current, false, nil - case rpc.FinalizedBlockNumber: - bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.finalized)) - return &bn, true, nil - case rpc.SafeBlockNumber: - bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.safe)) - return &bn, true, nil - case rpc.LatestBlockNumber: - bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.latest)) - return &bn, true, nil - default: - if current.BlockNumber.Int64() > int64(rctx.latest) { - return nil, false, ErrRewriteBlockOutOfRange - } - } - - return current, false, nil -}
diff --git OP/proxyd/rewriter_test.go CELO/proxyd/rewriter_test.go deleted file mode 100644 index 1f0d80ba25c99f7d39988dd216a20e1aecd2c46b..0000000000000000000000000000000000000000 --- OP/proxyd/rewriter_test.go +++ /dev/null @@ -1,717 +0,0 @@ -package proxyd - -import ( - "encoding/json" - "strings" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/require" -) - -type args struct { - rctx RewriteContext - req *RPCReq - res *RPCRes -} - -type rewriteTest struct { - name string - args args - expected RewriteResult - expectedErr error - check func(*testing.T, args) -} - -func TestRewriteRequest(t *testing.T) { - tests := []rewriteTest{ - /* range scoped */ - { - name: "eth_getLogs fromBlock latest", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "latest"}})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []map[string]interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) - }, - }, - { - name: "eth_getLogs fromBlock within range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(55).String()}})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []map[string]interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, hexutil.Uint64(55).String(), p[0]["fromBlock"]) - }, - }, - { - name: "eth_getLogs fromBlock out of range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(111).String()}})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteBlockOutOfRange, - }, - { - name: "eth_getLogs toBlock latest", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": "latest"}})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []map[string]interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) - }, - }, - { - name: "eth_getLogs toBlock within range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": hexutil.Uint64(55).String()}})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []map[string]interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, hexutil.Uint64(55).String(), p[0]["toBlock"]) - }, - }, - { - name: "eth_getLogs toBlock out of range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": hexutil.Uint64(111).String()}})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteBlockOutOfRange, - }, - { - name: "eth_getLogs fromBlock, toBlock latest", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "latest", "toBlock": "latest"}})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []map[string]interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) - require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) - }, - }, - { - name: "eth_getLogs fromBlock, toBlock within range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(55).String(), "toBlock": hexutil.Uint64(77).String()}})}, - res: nil, - }, - expected: RewriteNone, - check: func(t *testing.T, args args) { - var p []map[string]interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, hexutil.Uint64(55).String(), p[0]["fromBlock"]) - require.Equal(t, hexutil.Uint64(77).String(), p[0]["toBlock"]) - }, - }, - { - name: "eth_getLogs fromBlock, toBlock out of range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(111).String(), "toBlock": hexutil.Uint64(222).String()}})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteBlockOutOfRange, - }, - { - name: "eth_getLogs fromBlock -> toBlock above max range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(20).String(), "toBlock": hexutil.Uint64(80).String()}})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteRangeTooLarge, - }, - { - name: "eth_getLogs earliest -> latest above max range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest", "toBlock": "latest"}})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteRangeTooLarge, - }, - { - name: "eth_getLogs earliest -> pending above max range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest", "toBlock": "pending"}})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteRangeTooLarge, - }, - { - name: "eth_getLogs earliest -> default above max range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest"}})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteRangeTooLarge, - }, - { - name: "eth_getLogs default -> latest within range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, - req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": "latest"}})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []map[string]interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) - require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) - }, - }, - /* required parameter at pos 0 */ - { - name: "debug_getRawReceipts latest", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{"latest"})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 1, len(p)) - require.Equal(t, hexutil.Uint64(100).String(), p[0]) - }, - }, - { - name: "debug_getRawReceipts within range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{hexutil.Uint64(55).String()})}, - res: nil, - }, - expected: RewriteNone, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 1, len(p)) - require.Equal(t, hexutil.Uint64(55).String(), p[0]) - }, - }, - { - name: "debug_getRawReceipts out of range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{hexutil.Uint64(111).String()})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteBlockOutOfRange, - }, - { - name: "debug_getRawReceipts missing parameter", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{})}, - res: nil, - }, - expected: RewriteNone, - }, - { - name: "debug_getRawReceipts with block hash", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"})}, - res: nil, - }, - expected: RewriteNone, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 1, len(p)) - require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", p[0]) - }, - }, - /* default block parameter */ - { - name: "eth_getCode omit block, should add", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123"})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 2, len(p)) - require.Equal(t, "0x123", p[0]) - bnh, err := remarshalBlockNumberOrHash(p[1]) - require.Nil(t, err) - require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) - }, - }, - { - name: "eth_getCode not enough params, should do nothing", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{})}, - res: nil, - }, - expected: RewriteNone, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 0, len(p)) - }, - }, - { - name: "eth_getCode latest", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", "latest"})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 2, len(p)) - require.Equal(t, "0x123", p[0]) - bnh, err := remarshalBlockNumberOrHash(p[1]) - require.Nil(t, err) - require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) - }, - }, - { - name: "eth_getCode within range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", hexutil.Uint64(55).String()})}, - res: nil, - }, - expected: RewriteNone, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 2, len(p)) - require.Equal(t, "0x123", p[0]) - require.Equal(t, hexutil.Uint64(55).String(), p[1]) - }, - }, - { - name: "eth_getCode out of range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", hexutil.Uint64(111).String()})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteBlockOutOfRange, - }, - /* default block parameter, at position 2 */ - { - name: "eth_getStorageAt omit block, should add", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5"})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 3, len(p)) - require.Equal(t, "0x123", p[0]) - require.Equal(t, "5", p[1]) - bnh, err := remarshalBlockNumberOrHash(p[2]) - require.Nil(t, err) - require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) - }, - }, - { - name: "eth_getStorageAt latest", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", "latest"})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 3, len(p)) - require.Equal(t, "0x123", p[0]) - require.Equal(t, "5", p[1]) - bnh, err := remarshalBlockNumberOrHash(p[2]) - require.Nil(t, err) - require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) - }, - }, - { - name: "eth_getStorageAt within range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", hexutil.Uint64(55).String()})}, - res: nil, - }, - expected: RewriteNone, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 3, len(p)) - require.Equal(t, "0x123", p[0]) - require.Equal(t, "5", p[1]) - require.Equal(t, hexutil.Uint64(55).String(), p[2]) - }, - }, - { - name: "eth_getStorageAt out of range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", hexutil.Uint64(111).String()})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteBlockOutOfRange, - }, - /* default block parameter, at position 0 */ - { - name: "eth_getBlockByNumber omit block, should add", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 1, len(p)) - require.Equal(t, hexutil.Uint64(100).String(), p[0]) - }, - }, - { - name: "eth_getBlockByNumber latest", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"latest"})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 1, len(p)) - require.Equal(t, hexutil.Uint64(100).String(), p[0]) - }, - }, - { - name: "eth_getBlockByNumber finalized", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100), finalized: hexutil.Uint64(55)}, - req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"finalized"})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 1, len(p)) - require.Equal(t, hexutil.Uint64(55).String(), p[0]) - }, - }, - { - name: "eth_getBlockByNumber safe", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100), safe: hexutil.Uint64(50)}, - req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"safe"})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 1, len(p)) - require.Equal(t, hexutil.Uint64(50).String(), p[0]) - }, - }, - { - name: "eth_getBlockByNumber within range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{hexutil.Uint64(55).String()})}, - res: nil, - }, - expected: RewriteNone, - check: func(t *testing.T, args args) { - var p []string - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 1, len(p)) - require.Equal(t, hexutil.Uint64(55).String(), p[0]) - }, - }, - { - name: "eth_getBlockByNumber out of range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{hexutil.Uint64(111).String()})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteBlockOutOfRange, - }, - { - name: "eth_getStorageAt using rpc.BlockNumberOrHash", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{ - "0xae851f927ee40de99aabb7461c00f9622ab91d60", - "0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08", - "0x1c4840bcb3de3ac403c0075b46c2c47d4396c5b624b6e1b2874ec04e8879b483"})}, - res: nil, - }, - expected: RewriteNone, - }, - // eip1898 - { - name: "eth_getStorageAt using rpc.BlockNumberOrHash at genesis (blockNumber)", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ - "0xae851f927ee40de99aabb7461c00f9622ab91d60", - "10", - map[string]interface{}{ - "blockNumber": "0x0", - }})}, - res: nil, - }, - expected: RewriteNone, - }, - { - name: "eth_getStorageAt using rpc.BlockNumberOrHash at genesis (hash)", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ - "0xae851f927ee40de99aabb7461c00f9622ab91d60", - "10", - map[string]interface{}{ - "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", - "requireCanonical": true, - }})}, - res: nil, - }, - expected: RewriteNone, - check: func(t *testing.T, args args) { - var p []interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 3, len(p)) - require.Equal(t, "0xae851f927ee40de99aabb7461c00f9622ab91d60", p[0]) - require.Equal(t, "10", p[1]) - bnh, err := remarshalBlockNumberOrHash(p[2]) - require.Nil(t, err) - require.Equal(t, rpc.BlockNumberOrHashWithHash(common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"), true), *bnh) - require.True(t, bnh.RequireCanonical) - }, - }, - { - name: "eth_getStorageAt using rpc.BlockNumberOrHash at latest (blockNumber)", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ - "0xae851f927ee40de99aabb7461c00f9622ab91d60", - "10", - map[string]interface{}{ - "blockNumber": "latest", - }})}, - res: nil, - }, - expected: RewriteOverrideRequest, - check: func(t *testing.T, args args) { - var p []interface{} - err := json.Unmarshal(args.req.Params, &p) - require.Nil(t, err) - require.Equal(t, 3, len(p)) - require.Equal(t, "0xae851f927ee40de99aabb7461c00f9622ab91d60", p[0]) - require.Equal(t, "10", p[1]) - bnh, err := remarshalBlockNumberOrHash(p[2]) - require.Nil(t, err) - require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) - }, - }, - { - name: "eth_getStorageAt using rpc.BlockNumberOrHash out of range", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ - "0xae851f927ee40de99aabb7461c00f9622ab91d60", - "10", - map[string]interface{}{ - "blockNumber": "0x111", - }})}, - res: nil, - }, - expected: RewriteOverrideError, - expectedErr: ErrRewriteBlockOutOfRange, - }, - } - - // generalize tests for other methods with same interface and behavior - tests = generalize(tests, "eth_getLogs", "eth_newFilter") - tests = generalize(tests, "eth_getCode", "eth_getBalance") - tests = generalize(tests, "eth_getCode", "eth_getTransactionCount") - tests = generalize(tests, "eth_getCode", "eth_call") - tests = generalize(tests, "eth_getBlockByNumber", "eth_getBlockTransactionCountByNumber") - tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleCountByBlockNumber") - tests = generalize(tests, "eth_getBlockByNumber", "eth_getTransactionByBlockNumberAndIndex") - tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleByBlockNumberAndIndex") - tests = generalize(tests, "eth_getStorageSlotAt", "eth_getProof") - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := RewriteRequest(tt.args.rctx, tt.args.req, tt.args.res) - if result != RewriteOverrideError { - require.Nil(t, err) - require.Equal(t, tt.expected, result) - } else { - require.Equal(t, tt.expectedErr, err) - } - if tt.check != nil { - tt.check(t, tt.args) - } - }) - } -} - -func generalize(tests []rewriteTest, baseMethod string, generalizedMethod string) []rewriteTest { - newCases := make([]rewriteTest, 0) - for _, t := range tests { - if t.args.req.Method == baseMethod { - newName := strings.Replace(t.name, baseMethod, generalizedMethod, -1) - var req *RPCReq - var res *RPCRes - - if t.args.req != nil { - req = &RPCReq{ - JSONRPC: t.args.req.JSONRPC, - Method: generalizedMethod, - Params: t.args.req.Params, - ID: t.args.req.ID, - } - } - - if t.args.res != nil { - res = &RPCRes{ - JSONRPC: t.args.res.JSONRPC, - Result: t.args.res.Result, - Error: t.args.res.Error, - ID: t.args.res.ID, - } - } - newCases = append(newCases, rewriteTest{ - name: newName, - args: args{ - rctx: t.args.rctx, - req: req, - res: res, - }, - expected: t.expected, - expectedErr: t.expectedErr, - check: t.check, - }) - } - } - return append(tests, newCases...) -} - -func TestRewriteResponse(t *testing.T) { - type args struct { - rctx RewriteContext - req *RPCReq - res *RPCRes - } - tests := []struct { - name string - args args - expected RewriteResult - check func(*testing.T, args) - }{ - { - name: "eth_blockNumber latest", - args: args{ - rctx: RewriteContext{latest: hexutil.Uint64(100)}, - req: &RPCReq{Method: "eth_blockNumber"}, - res: &RPCRes{Result: hexutil.Uint64(200)}, - }, - expected: RewriteOverrideResponse, - check: func(t *testing.T, args args) { - require.Equal(t, args.res.Result, hexutil.Uint64(100)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := RewriteResponse(tt.args.rctx, tt.args.req, tt.args.res) - require.Nil(t, err) - require.Equal(t, tt.expected, result) - if tt.check != nil { - tt.check(t, tt.args) - } - }) - } -}
(deleted)
+0
-170
diff --git OP/proxyd/rpc.go CELO/proxyd/rpc.go deleted file mode 100644 index 902e26699b4977f1d75b1d59818ee4d34e7d02ce..0000000000000000000000000000000000000000 --- OP/proxyd/rpc.go +++ /dev/null @@ -1,170 +0,0 @@ -package proxyd - -import ( - "encoding/json" - "io" - "strings" -) - -type RPCReq struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params json.RawMessage `json:"params"` - ID json.RawMessage `json:"id"` -} - -type RPCRes struct { - JSONRPC string - Result interface{} - Error *RPCErr - ID json.RawMessage -} - -type rpcResJSON struct { - JSONRPC string `json:"jsonrpc"` - Result interface{} `json:"result,omitempty"` - Error *RPCErr `json:"error,omitempty"` - ID json.RawMessage `json:"id"` -} - -type nullResultRPCRes struct { - JSONRPC string `json:"jsonrpc"` - Result interface{} `json:"result"` - ID json.RawMessage `json:"id"` -} - -func (r *RPCRes) IsError() bool { - return r.Error != nil -} - -func (r *RPCRes) MarshalJSON() ([]byte, error) { - if r.Result == nil && r.Error == nil { - return json.Marshal(&nullResultRPCRes{ - JSONRPC: r.JSONRPC, - Result: nil, - ID: r.ID, - }) - } - - return json.Marshal(&rpcResJSON{ - JSONRPC: r.JSONRPC, - Result: r.Result, - Error: r.Error, - ID: r.ID, - }) -} - -type RPCErr struct { - Code int `json:"code"` - Message string `json:"message"` - Data string `json:"data,omitempty"` - HTTPErrorCode int `json:"-"` -} - -func (r *RPCErr) Error() string { - return r.Message -} - -func (r *RPCErr) Clone() *RPCErr { - return &RPCErr{ - Code: r.Code, - Message: r.Message, - HTTPErrorCode: r.HTTPErrorCode, - } -} - -func IsValidID(id json.RawMessage) bool { - // handle the case where the ID is a string - if strings.HasPrefix(string(id), "\"") && strings.HasSuffix(string(id), "\"") { - return len(id) > 2 - } - - // technically allows a boolean/null ID, but so does Geth - // https://github.com/ethereum/go-ethereum/blob/master/rpc/json.go#L72 - return len(id) > 0 && id[0] != '{' && id[0] != '[' -} - -func ParseRPCReq(body []byte) (*RPCReq, error) { - req := new(RPCReq) - if err := json.Unmarshal(body, req); err != nil { - return nil, ErrParseErr - } - - return req, nil -} - -func ParseBatchRPCReq(body []byte) ([]json.RawMessage, error) { - batch := make([]json.RawMessage, 0) - if err := json.Unmarshal(body, &batch); err != nil { - return nil, err - } - - return batch, nil -} - -func ParseRPCRes(r io.Reader) (*RPCRes, error) { - body, err := io.ReadAll(r) - if err != nil { - return nil, wrapErr(err, "error reading RPC response") - } - - res := new(RPCRes) - if err := json.Unmarshal(body, res); err != nil { - return nil, wrapErr(err, "error unmarshalling RPC response") - } - - return res, nil -} - -func ValidateRPCReq(req *RPCReq) error { - if req.JSONRPC != JSONRPCVersion { - return ErrInvalidRequest("invalid JSON-RPC version") - } - - if req.Method == "" { - return ErrInvalidRequest("no method specified") - } - - if !IsValidID(req.ID) { - return ErrInvalidRequest("invalid ID") - } - - return nil -} - -func NewRPCErrorRes(id json.RawMessage, err error) *RPCRes { - var rpcErr *RPCErr - if rr, ok := err.(*RPCErr); ok { - rpcErr = rr - } else { - rpcErr = &RPCErr{ - Code: JSONRPCErrorInternal, - Message: err.Error(), - } - } - - return &RPCRes{ - JSONRPC: JSONRPCVersion, - Error: rpcErr, - ID: id, - } -} - -func NewRPCRes(id json.RawMessage, result interface{}) *RPCRes { - return &RPCRes{ - JSONRPC: JSONRPCVersion, - Result: result, - ID: id, - } -} - -func IsBatch(raw []byte) bool { - for _, c := range raw { - // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt) - if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d { - continue - } - return c == '[' - } - return false -}
(deleted)
+0
-89
diff --git OP/proxyd/rpc_test.go CELO/proxyd/rpc_test.go deleted file mode 100644 index e30fe9361a6b2f8943450917dd15024438a1904c..0000000000000000000000000000000000000000 --- OP/proxyd/rpc_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package proxyd - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestRPCResJSON(t *testing.T) { - tests := []struct { - name string - in *RPCRes - out string - }{ - { - "string result", - &RPCRes{ - JSONRPC: JSONRPCVersion, - Result: "foobar", - ID: []byte("123"), - }, - `{"jsonrpc":"2.0","result":"foobar","id":123}`, - }, - { - "object result", - &RPCRes{ - JSONRPC: JSONRPCVersion, - Result: struct { - Str string `json:"str"` - }{ - "test", - }, - ID: []byte("123"), - }, - `{"jsonrpc":"2.0","result":{"str":"test"},"id":123}`, - }, - { - "nil result", - &RPCRes{ - JSONRPC: JSONRPCVersion, - Result: nil, - ID: []byte("123"), - }, - `{"jsonrpc":"2.0","result":null,"id":123}`, - }, - { - "error result without data", - &RPCRes{ - JSONRPC: JSONRPCVersion, - Error: &RPCErr{ - Code: 1234, - Message: "test err", - }, - ID: []byte("123"), - }, - `{"jsonrpc":"2.0","error":{"code":1234,"message":"test err"},"id":123}`, - }, - { - "error result with data", - &RPCRes{ - JSONRPC: JSONRPCVersion, - Error: &RPCErr{ - Code: 1234, - Message: "test err", - Data: "revert", - }, - ID: []byte("123"), - }, - `{"jsonrpc":"2.0","error":{"code":1234,"message":"test err","data":"revert"},"id":123}`, - }, - { - "string ID", - &RPCRes{ - JSONRPC: JSONRPCVersion, - Result: "foobar", - ID: []byte("\"123\""), - }, - `{"jsonrpc":"2.0","result":"foobar","id":"123"}`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - out, err := json.Marshal(tt.in) - require.NoError(t, err) - require.Equal(t, tt.out, string(out)) - }) - } -}
(deleted)
+0
-877
diff --git OP/proxyd/server.go CELO/proxyd/server.go deleted file mode 100644 index 527c2e6c1ff8f42afeda8309dfbaf0dc09dba80e..0000000000000000000000000000000000000000 --- OP/proxyd/server.go +++ /dev/null @@ -1,877 +0,0 @@ -package proxyd - -import ( - "context" - "crypto/rand" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "math" - "math/big" - "net/http" - "regexp" - "strconv" - "strings" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/txpool" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/gorilla/mux" - "github.com/gorilla/websocket" - "github.com/prometheus/client_golang/prometheus" - "github.com/redis/go-redis/v9" - "github.com/rs/cors" - "github.com/syndtr/goleveldb/leveldb/opt" -) - -const ( - ContextKeyAuth = "authorization" - ContextKeyReqID = "req_id" - ContextKeyXForwardedFor = "x_forwarded_for" - DefaultMaxBatchRPCCallsLimit = 100 - MaxBatchRPCCallsHardLimit = 1000 - cacheStatusHdr = "X-Proxyd-Cache-Status" - defaultRPCTimeout = 10 * time.Second - defaultBodySizeLimit = 256 * opt.KiB - defaultWSHandshakeTimeout = 10 * time.Second - defaultWSReadTimeout = 2 * time.Minute - defaultWSWriteTimeout = 10 * time.Second - defaultCacheTtl = 1 * time.Hour - maxRequestBodyLogLen = 2000 - defaultMaxUpstreamBatchSize = 10 - defaultRateLimitHeader = "X-Forwarded-For" -) - -var emptyArrayResponse = json.RawMessage("[]") - -type Server struct { - BackendGroups map[string]*BackendGroup - wsBackendGroup *BackendGroup - wsMethodWhitelist *StringSet - rpcMethodMappings map[string]string - maxBodySize int64 - enableRequestLog bool - maxRequestBodyLogLen int - authenticatedPaths map[string]string - timeout time.Duration - maxUpstreamBatchSize int - maxBatchSize int - enableServedByHeader bool - upgrader *websocket.Upgrader - mainLim FrontendRateLimiter - overrideLims map[string]FrontendRateLimiter - senderLim FrontendRateLimiter - allowedChainIds []*big.Int - limExemptOrigins []*regexp.Regexp - limExemptUserAgents []*regexp.Regexp - globallyLimitedMethods map[string]bool - rpcServer *http.Server - wsServer *http.Server - cache RPCCache - srvMu sync.Mutex - rateLimitHeader string -} - -type limiterFunc func(method string) bool - -func NewServer( - backendGroups map[string]*BackendGroup, - wsBackendGroup *BackendGroup, - wsMethodWhitelist *StringSet, - rpcMethodMappings map[string]string, - maxBodySize int64, - authenticatedPaths map[string]string, - timeout time.Duration, - maxUpstreamBatchSize int, - enableServedByHeader bool, - cache RPCCache, - rateLimitConfig RateLimitConfig, - senderRateLimitConfig SenderRateLimitConfig, - enableRequestLog bool, - maxRequestBodyLogLen int, - maxBatchSize int, - redisClient *redis.Client, -) (*Server, error) { - if cache == nil { - cache = &NoopRPCCache{} - } - - if maxBodySize == 0 { - maxBodySize = defaultBodySizeLimit - } - - if timeout == 0 { - timeout = defaultRPCTimeout - } - - if maxUpstreamBatchSize == 0 { - maxUpstreamBatchSize = defaultMaxUpstreamBatchSize - } - - if maxBatchSize == 0 { - maxBatchSize = DefaultMaxBatchRPCCallsLimit - } - - if maxBatchSize > MaxBatchRPCCallsHardLimit { - maxBatchSize = MaxBatchRPCCallsHardLimit - } - - limiterFactory := func(dur time.Duration, max int, prefix string) FrontendRateLimiter { - if rateLimitConfig.UseRedis { - return NewRedisFrontendRateLimiter(redisClient, dur, max, prefix) - } - - return NewMemoryFrontendRateLimit(dur, max) - } - - var mainLim FrontendRateLimiter - limExemptOrigins := make([]*regexp.Regexp, 0) - limExemptUserAgents := make([]*regexp.Regexp, 0) - if rateLimitConfig.BaseRate > 0 { - mainLim = limiterFactory(time.Duration(rateLimitConfig.BaseInterval), rateLimitConfig.BaseRate, "main") - for _, origin := range rateLimitConfig.ExemptOrigins { - pattern, err := regexp.Compile(origin) - if err != nil { - return nil, err - } - limExemptOrigins = append(limExemptOrigins, pattern) - } - for _, agent := range rateLimitConfig.ExemptUserAgents { - pattern, err := regexp.Compile(agent) - if err != nil { - return nil, err - } - limExemptUserAgents = append(limExemptUserAgents, pattern) - } - } else { - mainLim = NoopFrontendRateLimiter - } - - overrideLims := make(map[string]FrontendRateLimiter) - globalMethodLims := make(map[string]bool) - for method, override := range rateLimitConfig.MethodOverrides { - overrideLims[method] = limiterFactory(time.Duration(override.Interval), override.Limit, method) - - if override.Global { - globalMethodLims[method] = true - } - } - var senderLim FrontendRateLimiter - if senderRateLimitConfig.Enabled { - senderLim = limiterFactory(time.Duration(senderRateLimitConfig.Interval), senderRateLimitConfig.Limit, "senders") - } - - rateLimitHeader := defaultRateLimitHeader - if rateLimitConfig.IPHeaderOverride != "" { - rateLimitHeader = rateLimitConfig.IPHeaderOverride - } - - return &Server{ - BackendGroups: backendGroups, - wsBackendGroup: wsBackendGroup, - wsMethodWhitelist: wsMethodWhitelist, - rpcMethodMappings: rpcMethodMappings, - maxBodySize: maxBodySize, - authenticatedPaths: authenticatedPaths, - timeout: timeout, - maxUpstreamBatchSize: maxUpstreamBatchSize, - enableServedByHeader: enableServedByHeader, - cache: cache, - enableRequestLog: enableRequestLog, - maxRequestBodyLogLen: maxRequestBodyLogLen, - maxBatchSize: maxBatchSize, - upgrader: &websocket.Upgrader{ - HandshakeTimeout: defaultWSHandshakeTimeout, - }, - mainLim: mainLim, - overrideLims: overrideLims, - globallyLimitedMethods: globalMethodLims, - senderLim: senderLim, - allowedChainIds: senderRateLimitConfig.AllowedChainIds, - limExemptOrigins: limExemptOrigins, - limExemptUserAgents: limExemptUserAgents, - rateLimitHeader: rateLimitHeader, - }, nil -} - -func (s *Server) RPCListenAndServe(host string, port int) error { - s.srvMu.Lock() - hdlr := mux.NewRouter() - hdlr.HandleFunc("/healthz", s.HandleHealthz).Methods("GET") - hdlr.HandleFunc("/", s.HandleRPC).Methods("POST") - hdlr.HandleFunc("/{authorization}", s.HandleRPC).Methods("POST") - c := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - }) - addr := fmt.Sprintf("%s:%d", host, port) - s.rpcServer = &http.Server{ - Handler: instrumentedHdlr(c.Handler(hdlr)), - Addr: addr, - } - log.Info("starting HTTP server", "addr", addr) - s.srvMu.Unlock() - return s.rpcServer.ListenAndServe() -} - -func (s *Server) WSListenAndServe(host string, port int) error { - s.srvMu.Lock() - hdlr := mux.NewRouter() - hdlr.HandleFunc("/", s.HandleWS) - hdlr.HandleFunc("/{authorization}", s.HandleWS) - c := cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - }) - addr := fmt.Sprintf("%s:%d", host, port) - s.wsServer = &http.Server{ - Handler: instrumentedHdlr(c.Handler(hdlr)), - Addr: addr, - } - log.Info("starting WS server", "addr", addr) - s.srvMu.Unlock() - return s.wsServer.ListenAndServe() -} - -func (s *Server) Shutdown() { - s.srvMu.Lock() - defer s.srvMu.Unlock() - if s.rpcServer != nil { - _ = s.rpcServer.Shutdown(context.Background()) - } - if s.wsServer != nil { - _ = s.wsServer.Shutdown(context.Background()) - } - for _, bg := range s.BackendGroups { - bg.Shutdown() - } -} - -func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte("OK")) -} - -func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { - ctx := s.populateContext(w, r) - if ctx == nil { - return - } - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, s.timeout) - defer cancel() - - origin := r.Header.Get("Origin") - userAgent := r.Header.Get("User-Agent") - // Use XFF in context since it will automatically be replaced by the remote IP - xff := stripXFF(GetXForwardedFor(ctx)) - isUnlimitedOrigin := s.isUnlimitedOrigin(origin) - isUnlimitedUserAgent := s.isUnlimitedUserAgent(userAgent) - - if xff == "" { - writeRPCError(ctx, w, nil, ErrInvalidRequest("request does not include a remote IP")) - return - } - - isLimited := func(method string) bool { - isGloballyLimitedMethod := s.isGlobalLimit(method) - if !isGloballyLimitedMethod && (isUnlimitedOrigin || isUnlimitedUserAgent) { - return false - } - - var lim FrontendRateLimiter - if method == "" { - lim = s.mainLim - } else { - lim = s.overrideLims[method] - } - - if lim == nil { - return false - } - - ok, err := lim.Take(ctx, xff) - if err != nil { - log.Warn("error taking rate limit", "err", err) - return true - } - return !ok - } - - if isLimited("") { - RecordRPCError(ctx, BackendProxyd, "unknown", ErrOverRateLimit) - log.Warn( - "rate limited request", - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - "user_agent", userAgent, - "origin", origin, - "remote_ip", xff, - ) - writeRPCError(ctx, w, nil, ErrOverRateLimit) - return - } - - log.Info( - "received RPC request", - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - "user_agent", userAgent, - "origin", origin, - "remote_ip", xff, - ) - - body, err := io.ReadAll(LimitReader(r.Body, s.maxBodySize)) - if errors.Is(err, ErrLimitReaderOverLimit) { - log.Error("request body too large", "req_id", GetReqID(ctx)) - RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrRequestBodyTooLarge) - writeRPCError(ctx, w, nil, ErrRequestBodyTooLarge) - return - } - if err != nil { - log.Error("error reading request body", "err", err) - writeRPCError(ctx, w, nil, ErrInternal) - return - } - RecordRequestPayloadSize(ctx, len(body)) - - if s.enableRequestLog { - log.Info("Raw RPC request", - "body", truncate(string(body), s.maxRequestBodyLogLen), - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - ) - } - - if IsBatch(body) { - reqs, err := ParseBatchRPCReq(body) - if err != nil { - log.Error("error parsing batch RPC request", "err", err) - RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) - writeRPCError(ctx, w, nil, ErrParseErr) - return - } - - RecordBatchSize(len(reqs)) - - if len(reqs) > s.maxBatchSize { - RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrTooManyBatchRequests) - writeRPCError(ctx, w, nil, ErrTooManyBatchRequests) - return - } - - if len(reqs) == 0 { - writeRPCError(ctx, w, nil, ErrInvalidRequest("must specify at least one batch call")) - return - } - - batchRes, batchContainsCached, servedBy, err := s.handleBatchRPC(ctx, reqs, isLimited, true) - if err == context.DeadlineExceeded { - writeRPCError(ctx, w, nil, ErrGatewayTimeout) - return - } - if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || - errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { - writeRPCError(ctx, w, nil, ErrInvalidRequest(err.Error())) - return - } - if err != nil { - writeRPCError(ctx, w, nil, ErrInternal) - return - } - if s.enableServedByHeader { - w.Header().Set("x-served-by", servedBy) - } - setCacheHeader(w, batchContainsCached) - writeBatchRPCRes(ctx, w, batchRes) - return - } - - rawBody := json.RawMessage(body) - backendRes, cached, servedBy, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, isLimited, false) - if err != nil { - if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || - errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { - writeRPCError(ctx, w, nil, ErrInvalidRequest(err.Error())) - return - } - writeRPCError(ctx, w, nil, ErrInternal) - return - } - if s.enableServedByHeader { - w.Header().Set("x-served-by", servedBy) - } - setCacheHeader(w, cached) - writeRPCRes(ctx, w, backendRes[0]) -} - -func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isLimited limiterFunc, isBatch bool) ([]*RPCRes, bool, string, error) { - // A request set is transformed into groups of batches. - // Each batch group maps to a forwarded JSON-RPC batch request (subject to maxUpstreamBatchSize constraints) - // A groupID is used to decouple Requests that have duplicate ID so they're not part of the same batch that's - // forwarded to the backend. This is done to ensure that the order of JSON-RPC Responses match the Request order - // as the backend MAY return Responses out of order. - // NOTE: Duplicate request ids induces 1-sized JSON-RPC batches - type batchGroup struct { - groupID int - backendGroup string - } - - responses := make([]*RPCRes, len(reqs)) - batches := make(map[batchGroup][]batchElem) - ids := make(map[string]int, len(reqs)) - - for i := range reqs { - parsedReq, err := ParseRPCReq(reqs[i]) - if err != nil { - log.Info("error parsing RPC call", "source", "rpc", "err", err) - responses[i] = NewRPCErrorRes(nil, err) - continue - } - - // Simple health check - if len(reqs) == 1 && parsedReq.Method == proxydHealthzMethod { - res := &RPCRes{ - ID: parsedReq.ID, - JSONRPC: JSONRPCVersion, - Result: "OK", - } - return []*RPCRes{res}, false, "", nil - } - - if err := ValidateRPCReq(parsedReq); err != nil { - RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) - responses[i] = NewRPCErrorRes(nil, err) - continue - } - - if parsedReq.Method == "eth_accounts" { - RecordRPCForward(ctx, BackendProxyd, "eth_accounts", RPCRequestSourceHTTP) - responses[i] = NewRPCRes(parsedReq.ID, emptyArrayResponse) - continue - } - - group := s.rpcMethodMappings[parsedReq.Method] - if group == "" { - // use unknown below to prevent DOS vector that fills up memory - // with arbitrary method names. - log.Info( - "blocked request for non-whitelisted method", - "source", "rpc", - "req_id", GetReqID(ctx), - "method", parsedReq.Method, - ) - RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrMethodNotWhitelisted) - responses[i] = NewRPCErrorRes(parsedReq.ID, ErrMethodNotWhitelisted) - continue - } - - // Take rate limit for specific methods. - // NOTE: eventually, this should apply to all batch requests. However, - // since we don't have data right now on the size of each batch, we - // only apply this to the methods that have an additional rate limit. - if _, ok := s.overrideLims[parsedReq.Method]; ok && isLimited(parsedReq.Method) { - log.Info( - "rate limited specific RPC", - "source", "rpc", - "req_id", GetReqID(ctx), - "method", parsedReq.Method, - ) - RecordRPCError(ctx, BackendProxyd, parsedReq.Method, ErrOverRateLimit) - responses[i] = NewRPCErrorRes(parsedReq.ID, ErrOverRateLimit) - continue - } - - // Apply a sender-based rate limit if it is enabled. Note that sender-based rate - // limits apply regardless of origin or user-agent. As such, they don't use the - // isLimited method. - if parsedReq.Method == "eth_sendRawTransaction" && s.senderLim != nil { - if err := s.rateLimitSender(ctx, parsedReq); err != nil { - RecordRPCError(ctx, BackendProxyd, parsedReq.Method, err) - responses[i] = NewRPCErrorRes(parsedReq.ID, err) - continue - } - } - - id := string(parsedReq.ID) - // If this is a duplicate Request ID, move the Request to a new batchGroup - ids[id]++ - batchGroupID := ids[id] - batchGroup := batchGroup{groupID: batchGroupID, backendGroup: group} - batches[batchGroup] = append(batches[batchGroup], batchElem{parsedReq, i}) - } - - servedBy := make(map[string]bool, 0) - var cached bool - for group, batch := range batches { - var cacheMisses []batchElem - - for _, req := range batch { - backendRes, _ := s.cache.GetRPC(ctx, req.Req) - if backendRes != nil { - responses[req.Index] = backendRes - cached = true - } else { - cacheMisses = append(cacheMisses, req) - } - } - - // Create minibatches - each minibatch must be no larger than the maxUpstreamBatchSize - numBatches := int(math.Ceil(float64(len(cacheMisses)) / float64(s.maxUpstreamBatchSize))) - for i := 0; i < numBatches; i++ { - if ctx.Err() == context.DeadlineExceeded { - log.Info("short-circuiting batch RPC", - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - "batch_index", i, - ) - batchRPCShortCircuitsTotal.Inc() - return nil, false, "", context.DeadlineExceeded - } - - start := i * s.maxUpstreamBatchSize - end := int(math.Min(float64(start+s.maxUpstreamBatchSize), float64(len(cacheMisses)))) - elems := cacheMisses[start:end] - res, sb, err := s.BackendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) - servedBy[sb] = true - if err != nil { - if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || - errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { - return nil, false, "", err - } - log.Error( - "error forwarding RPC batch", - "batch_size", len(elems), - "backend_group", group, - "req_id", GetReqID(ctx), - "err", err, - ) - res = nil - for _, elem := range elems { - res = append(res, NewRPCErrorRes(elem.Req.ID, err)) - } - } - - for i := range elems { - responses[elems[i].Index] = res[i] - - // TODO(inphi): batch put these - if res[i].Error == nil && res[i].Result != nil { - if err := s.cache.PutRPC(ctx, elems[i].Req, res[i]); err != nil { - log.Warn( - "cache put error", - "req_id", GetReqID(ctx), - "err", err, - ) - } - } - } - } - } - - servedByString := "" - for sb, _ := range servedBy { - if servedByString != "" { - servedByString += ", " - } - servedByString += sb - } - - return responses, cached, servedByString, nil -} - -func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { - ctx := s.populateContext(w, r) - if ctx == nil { - return - } - - log.Info("received WS connection", "req_id", GetReqID(ctx)) - - clientConn, err := s.upgrader.Upgrade(w, r, nil) - if err != nil { - log.Error("error upgrading client conn", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) - return - } - clientConn.SetReadLimit(s.maxBodySize) - - proxier, err := s.wsBackendGroup.ProxyWS(ctx, clientConn, s.wsMethodWhitelist) - if err != nil { - if errors.Is(err, ErrNoBackends) { - RecordUnserviceableRequest(ctx, RPCRequestSourceWS) - } - log.Error("error dialing ws backend", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) - clientConn.Close() - return - } - - activeClientWsConnsGauge.WithLabelValues(GetAuthCtx(ctx)).Inc() - go func() { - // Below call blocks so run it in a goroutine. - if err := proxier.Proxy(ctx); err != nil { - log.Error("error proxying websocket", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) - } - activeClientWsConnsGauge.WithLabelValues(GetAuthCtx(ctx)).Dec() - }() - - log.Info("accepted WS connection", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx)) -} - -func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context.Context { - vars := mux.Vars(r) - authorization := vars["authorization"] - xff := r.Header.Get(s.rateLimitHeader) - if xff == "" { - ipPort := strings.Split(r.RemoteAddr, ":") - if len(ipPort) == 2 { - xff = ipPort[0] - } - } - ctx := context.WithValue(r.Context(), ContextKeyXForwardedFor, xff) // nolint:staticcheck - - if len(s.authenticatedPaths) > 0 { - if authorization == "" || s.authenticatedPaths[authorization] == "" { - log.Info("blocked unauthorized request", "authorization", authorization) - httpResponseCodesTotal.WithLabelValues("401").Inc() - w.WriteHeader(401) - return nil - } - - ctx = context.WithValue(ctx, ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck - } - - return context.WithValue( - ctx, - ContextKeyReqID, // nolint:staticcheck - randStr(10), - ) -} - -func randStr(l int) string { - b := make([]byte, l) - if _, err := rand.Read(b); err != nil { - panic(err) - } - return hex.EncodeToString(b) -} - -func (s *Server) isUnlimitedOrigin(origin string) bool { - for _, pat := range s.limExemptOrigins { - if pat.MatchString(origin) { - return true - } - } - - return false -} - -func (s *Server) isUnlimitedUserAgent(origin string) bool { - for _, pat := range s.limExemptUserAgents { - if pat.MatchString(origin) { - return true - } - } - return false -} - -func (s *Server) isGlobalLimit(method string) bool { - return s.globallyLimitedMethods[method] -} - -func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { - var params []string - if err := json.Unmarshal(req.Params, &params); err != nil { - log.Debug("error unmarshalling raw transaction params", "err", err, "req_Id", GetReqID(ctx)) - return ErrParseErr - } - - if len(params) != 1 { - log.Debug("raw transaction request has invalid number of params", "req_id", GetReqID(ctx)) - // The error below is identical to the one Geth responds with. - return ErrInvalidParams("missing value for required argument 0") - } - - var data hexutil.Bytes - if err := data.UnmarshalText([]byte(params[0])); err != nil { - log.Debug("error decoding raw tx data", "err", err, "req_id", GetReqID(ctx)) - // Geth returns the raw error from UnmarshalText. - return ErrInvalidParams(err.Error()) - } - - // Inflates a types.Transaction object from the transaction's raw bytes. - tx := new(types.Transaction) - if err := tx.UnmarshalBinary(data); err != nil { - log.Debug("could not unmarshal transaction", "err", err, "req_id", GetReqID(ctx)) - return ErrInvalidParams(err.Error()) - } - - // Check if the transaction is for the expected chain, - // otherwise reject before rate limiting to avoid replay attacks. - if !s.isAllowedChainId(tx.ChainId()) { - log.Debug("chain id is not allowed", "req_id", GetReqID(ctx)) - return txpool.ErrInvalidSender - } - - // Convert the transaction into a Message object so that we can get the - // sender. This method performs an ecrecover, which can be expensive. - msg, err := core.TransactionToMessage(tx, types.LatestSignerForChainID(tx.ChainId()), nil) - if err != nil { - log.Debug("could not get message from transaction", "err", err, "req_id", GetReqID(ctx)) - return ErrInvalidParams(err.Error()) - } - ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From.Hex(), tx.Nonce())) - if err != nil { - log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx)) - return ErrInternal - } - if !ok { - log.Debug("sender rate limit exceeded", "sender", msg.From.Hex(), "req_id", GetReqID(ctx)) - return ErrOverSenderRateLimit - } - - return nil -} - -func (s *Server) isAllowedChainId(chainId *big.Int) bool { - if s.allowedChainIds == nil || len(s.allowedChainIds) == 0 { - return true - } - for _, id := range s.allowedChainIds { - if chainId.Cmp(id) == 0 { - return true - } - } - return false -} - -func setCacheHeader(w http.ResponseWriter, cached bool) { - if cached { - w.Header().Set(cacheStatusHdr, "HIT") - } else { - w.Header().Set(cacheStatusHdr, "MISS") - } -} - -func writeRPCError(ctx context.Context, w http.ResponseWriter, id json.RawMessage, err error) { - var res *RPCRes - if r, ok := err.(*RPCErr); ok { - res = NewRPCErrorRes(id, r) - } else { - res = NewRPCErrorRes(id, ErrInternal) - } - writeRPCRes(ctx, w, res) -} - -func writeRPCRes(ctx context.Context, w http.ResponseWriter, res *RPCRes) { - statusCode := 200 - if res.IsError() && res.Error.HTTPErrorCode != 0 { - statusCode = res.Error.HTTPErrorCode - } - - w.Header().Set("content-type", "application/json") - w.WriteHeader(statusCode) - ww := &recordLenWriter{Writer: w} - enc := json.NewEncoder(ww) - if err := enc.Encode(res); err != nil { - log.Error("error writing rpc response", "err", err) - RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) - return - } - httpResponseCodesTotal.WithLabelValues(strconv.Itoa(statusCode)).Inc() - RecordResponsePayloadSize(ctx, ww.Len) -} - -func writeBatchRPCRes(ctx context.Context, w http.ResponseWriter, res []*RPCRes) { - w.Header().Set("content-type", "application/json") - w.WriteHeader(200) - ww := &recordLenWriter{Writer: w} - enc := json.NewEncoder(ww) - if err := enc.Encode(res); err != nil { - log.Error("error writing batch rpc response", "err", err) - RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) - return - } - RecordResponsePayloadSize(ctx, ww.Len) -} - -func instrumentedHdlr(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - respTimer := prometheus.NewTimer(httpRequestDurationSumm) - h.ServeHTTP(w, r) - respTimer.ObserveDuration() - } -} - -func GetAuthCtx(ctx context.Context) string { - authUser, ok := ctx.Value(ContextKeyAuth).(string) - if !ok { - return "none" - } - - return authUser -} - -func GetReqID(ctx context.Context) string { - reqId, ok := ctx.Value(ContextKeyReqID).(string) - if !ok { - return "" - } - return reqId -} - -func GetXForwardedFor(ctx context.Context) string { - xff, ok := ctx.Value(ContextKeyXForwardedFor).(string) - if !ok { - return "" - } - return xff -} - -type recordLenWriter struct { - io.Writer - Len int -} - -func (w *recordLenWriter) Write(p []byte) (n int, err error) { - n, err = w.Writer.Write(p) - w.Len += n - return -} - -type NoopRPCCache struct{} - -func (n *NoopRPCCache) GetRPC(context.Context, *RPCReq) (*RPCRes, error) { - return nil, nil -} - -func (n *NoopRPCCache) PutRPC(context.Context, *RPCReq, *RPCRes) error { - return nil -} - -func truncate(str string, maxLen int) string { - if maxLen == 0 { - maxLen = maxRequestBodyLogLen - } - - if len(str) > maxLen { - return str[:maxLen] + "..." - } else { - return str - } -} - -type batchElem struct { - Req *RPCReq - Index int -} - -func createBatchRequest(elems []batchElem) []*RPCReq { - batch := make([]*RPCReq, len(elems)) - for i := range elems { - batch[i] = elems[i].Req - } - return batch -}
(deleted)
+0
-56
diff --git OP/proxyd/string_set.go CELO/proxyd/string_set.go deleted file mode 100644 index 45823491961161eff6212039e746ef33f9b3c7b3..0000000000000000000000000000000000000000 --- OP/proxyd/string_set.go +++ /dev/null @@ -1,56 +0,0 @@ -package proxyd - -import "sync" - -type StringSet struct { - underlying map[string]bool - mtx sync.RWMutex -} - -func NewStringSet() *StringSet { - return &StringSet{ - underlying: make(map[string]bool), - } -} - -func NewStringSetFromStrings(in []string) *StringSet { - underlying := make(map[string]bool) - for _, str := range in { - underlying[str] = true - } - return &StringSet{ - underlying: underlying, - } -} - -func (s *StringSet) Has(test string) bool { - s.mtx.RLock() - defer s.mtx.RUnlock() - return s.underlying[test] -} - -func (s *StringSet) Add(str string) { - s.mtx.Lock() - defer s.mtx.Unlock() - s.underlying[str] = true -} - -func (s *StringSet) Entries() []string { - s.mtx.RLock() - defer s.mtx.RUnlock() - out := make([]string, len(s.underlying)) - var i int - for entry := range s.underlying { - out[i] = entry - i++ - } - return out -} - -func (s *StringSet) Extend(in []string) *StringSet { - out := NewStringSetFromStrings(in) - for k := range s.underlying { - out.Add(k) - } - return out -}
(deleted)
+0
-33
diff --git OP/proxyd/tls.go CELO/proxyd/tls.go deleted file mode 100644 index ed2bdaff44b4b9a06d4a5d358fd4c410efe67b00..0000000000000000000000000000000000000000 --- OP/proxyd/tls.go +++ /dev/null @@ -1,33 +0,0 @@ -package proxyd - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "os" -) - -func CreateTLSClient(ca string) (*tls.Config, error) { - pem, err := os.ReadFile(ca) - if err != nil { - return nil, wrapErr(err, "error reading CA") - } - - roots := x509.NewCertPool() - ok := roots.AppendCertsFromPEM(pem) - if !ok { - return nil, errors.New("error parsing TLS client cert") - } - - return &tls.Config{ - RootCAs: roots, - }, nil -} - -func ParseKeyPair(crt, key string) (tls.Certificate, error) { - cert, err := tls.LoadX509KeyPair(crt, key) - if err != nil { - return tls.Certificate{}, wrapErr(err, "error loading x509 key pair") - } - return cert, nil -}
diff --git OP/.envrc.example CELO/.envrc.example index 43ccf74842b63f71531e4c96b429a5ebbb0e6ea6..bd1473b503ce798ba008cc5702e46ee1a82f25bd 100644 --- OP/.envrc.example +++ CELO/.envrc.example @@ -66,3 +66,18 @@ # Private key to use for contract deployments, you don't need to worry about # this for the Getting Started guide. export PRIVATE_KEY= + +# CELO additional configuration +export ENABLE_GOVERNANCE=false +export FUNDS_DEV_ACCOUNTS=false +export USE_PLASMA=false +# Set to false if migrating state from a Celo L1. True for new testnets +export DEPLOY_CELO_CONTRACTS=false + +export USE_CUSTOM_GAS_TOKEN=true +# Set to "0x0000000000000000000000000000000000000000" when the contract +# should get deployed (via create2) on L1 as part of the deploy script. +# Set to the deployed contract address when already deployed on L1 +# This only works when USE_CUSTOM_GAS_TOKEN=true +export CUSTOM_GAS_TOKEN_ADDRESS=0x0000000000000000000000000000000000000000 +
diff --git OP/.gitignore CELO/.gitignore index 54c16a1f67c3f2dbeff42e8717eb6b828b106336..16640801b343664ded9a93a8f581128a5a6b8343 100644 --- OP/.gitignore +++ CELO/.gitignore @@ -47,3 +47,6 @@ __pycache__   # Ignore echidna artifacts crytic-export + +# vscode +.vscode/
diff --git OP/Makefile CELO/Makefile index 02ea6b71f14fae83154fe471d873eeefe773682c..673066822abc9673c75fad1b44177255e6cec991 100644 --- OP/Makefile +++ CELO/Makefile @@ -21,7 +21,6 @@ if [ -f "$$NVM_DIR/nvm.sh" ]; then \ . $$NVM_DIR/nvm.sh && nvm use; \ fi pnpm install:ci - pnpm prepare pnpm build .PHONY: build-ts   @@ -38,7 +37,7 @@ docker buildx bake \ --progress plain \ --load \ -f docker-bake.hcl \ - op-node op-batcher op-proposer op-challenger op-dispute-mon + op-node op-batcher op-proposer op-challenger op-dispute-mon op-supervisor .PHONY: golang-docker   docker-builder-clean:
diff --git OP/README.md CELO/README.md index 435fb5b46ca018beedb9fd77be1b319719f41730..b8f0ca0fcd9e1b2171656ceb21beaef4fb434e03 100644 --- OP/README.md +++ CELO/README.md @@ -28,18 +28,18 @@ <!-- END doctoc generated TOC please keep comment here to allow auto update -->   ## What is Optimism?   -[Optimism](https://www.optimism.io/) is a project dedicated to scaling Ethereum's technology and expanding its ability to coordinate people from across the world to build effective decentralized economies and governance systems. The [Optimism Collective](https://app.optimism.io/announcement) builds open-source software for running L2 blockchains and aims to address key governance and economic challenges in the wider cryptocurrency ecosystem. Optimism operates on the principle of **impact=profit**, the idea that individuals who positively impact the Collective should be proportionally rewarded with profit. **Change the incentives and you change the world.** +[Optimism](https://www.optimism.io/) is a project dedicated to scaling Ethereum's technology and expanding its ability to coordinate people from across the world to build effective decentralized economies and governance systems. The [Optimism Collective](https://www.optimism.io/vision) builds open-source software that powers scalable blockchains and aims to address key governance and economic challenges in the wider Ethereum ecosystem. Optimism operates on the principle of **impact=profit**, the idea that individuals who positively impact the Collective should be proportionally rewarded with profit. **Change the incentives and you change the world.**   -In this repository, you'll find numerous core components of the OP Stack, the decentralized software stack maintained by the Optimism Collective that powers Optimism and forms the backbone of blockchains like [OP Mainnet](https://explorer.optimism.io/) and [Base](https://base.org). Designed to be "aggressively open source," the OP Stack encourages you to explore, modify, extend, and test the code as needed. Although not all elements of the OP Stack are contained here, many of its essential components can be found within this repository. By collaborating on free, open software and shared standards, the Optimism Collective aims to prevent siloed software development and rapidly accelerate the development of the Ethereum ecosystem. Come contribute, build the future, and redefine power, together. +In this repository you'll find numerous core components of the OP Stack, the decentralized software stack maintained by the Optimism Collective that powers Optimism and forms the backbone of blockchains like [OP Mainnet](https://explorer.optimism.io/) and [Base](https://base.org). The OP Stack is designed to be aggressively open-source — you are welcome to explore, modify, and extend this code.   ## Documentation   - If you want to build on top of OP Mainnet, refer to the [Optimism Documentation](https://docs.optimism.io) -- If you want to build your own OP Stack based blockchain, refer to the [OP Stack Guide](https://docs.optimism.io/stack/getting-started), and make sure to understand this repository's [Development and Release Process](#development-and-release-process) +- If you want to build your own OP Stack based blockchain, refer to the [OP Stack Guide](https://docs.optimism.io/stack/getting-started) and make sure to understand this repository's [Development and Release Process](#development-and-release-process)   ## Specification   -If you're interested in the technical details of how Optimism works, refer to the [Optimism Protocol Specification](https://github.com/ethereum-optimism/specs). +Detailed specifications for the OP Stack can be found within the [OP Stack Specs](https://github.com/ethereum-optimism/specs) repository.   ## Community   @@ -48,15 +48,16 @@ Governance discussion can also be found on the [Optimism Governance Forum](https://gov.optimism.io/).   ## Contributing   -Read through [CONTRIBUTING.md](./CONTRIBUTING.md) for a general overview of the contributing process for this repository. -Use the [Developer Quick Start](./CONTRIBUTING.md#development-quick-start) to get your development environment set up to start working on the Optimism Monorepo. -Then check out the list of [Good First Issues](https://github.com/ethereum-optimism/optimism/issues?q=is:open+is:issue+label:D-good-first-issue) to find something fun to work on! -Typo fixes are welcome; however, please create a single commit with all of the typo fixes & batch as many fixes together in a PR as possible. Spammy PRs will be closed. +The OP Stack is a collaborative project. By collaborating on free, open software and shared standards, the Optimism Collective aims to prevent siloed software development and rapidly accelerate the development of the Ethereum ecosystem. Come contribute, build the future, and redefine power, together. + +[CONTRIBUTING.md](./CONTRIBUTING.md) contains a detailed explanation of the contributing process for this repository. Make sure to use the [Developer Quick Start](./CONTRIBUTING.md#development-quick-start) to properly set up your development environment. + +[Good First Issues](https://github.com/ethereum-optimism/optimism/issues?q=is:open+is:issue+label:D-good-first-issue) are a great place to look for tasks to tackle if you're not sure where to start.   ## Security Policy and Vulnerability Reporting   Please refer to the canonical [Security Policy](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md) document for detailed information about how to report vulnerabilities in this codebase. -Bounty hunters are encouraged to check out [the Optimism Immunefi bug bounty program](https://immunefi.com/bounty/optimism/). +Bounty hunters are encouraged to check out the [Optimism Immunefi bug bounty program](https://immunefi.com/bounty/optimism/). The Optimism Immunefi program offers up to $2,000,042 for in-scope critical vulnerabilities.   ## Directory Structure @@ -80,8 +81,8 @@ ├── <a href="./ops">ops</a>: Various operational packages ├── <a href="./ops-bedrock">ops-bedrock</a>: Bedrock devnet work ├── <a href="./packages">packages</a> │ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services -│ ├── <a href="./packages/contracts-bedrock">contracts-bedrock</a>: Bedrock smart contracts -│ ├── <a href="./packages/sdk">sdk</a>: provides a set of tools for interacting with Optimism +│ ├── <a href="./packages/contracts-bedrock">contracts-bedrock</a>: OP Stack smart contracts +│ ├── <a href="./packages/devnet-tasks">devnet-tasks</a>: Legacy Hardhat tasks used within devnet CI tests ├── <a href="./proxyd">proxyd</a>: Configurable RPC request router and proxy ├── <a href="./specs">specs</a>: Specs of the rollup starting at the Bedrock upgrade </pre> @@ -90,7 +91,7 @@ ## Development and Release Process   ### Overview   -Please read this section if you're planning to fork this repository, or make frequent PRs into this repository. +Please read this section carefully if you're planning to fork or make frequent PRs into this repository.   ### Production Releases   @@ -99,11 +100,11 @@ For example, an `op-node` release might be versioned as `op-node/v1.1.2`, and smart contract releases might be versioned as `op-contracts/v1.0.0`. Release candidates are versioned in the format `op-node/v1.1.2-rc.1`. We always start with `rc.1` rather than `rc`.   -For contract releases, refer to the GitHub release notes for a given release, which will list the specific contracts being released—not all contracts are considered production ready within a release, and many are under active development. +For contract releases, refer to the GitHub release notes for a given release which will list the specific contracts being released. Not all contracts are considered production ready within a release and many are under active development.   Tags of the form `v<semver>`, such as `v1.1.4`, indicate releases of all Go code only, and **DO NOT** include smart contracts. This naming scheme is required by Golang. -In the above list, this means these `v<semver` releases contain all `op-*` components, and exclude all `contracts-*` components. +In the above list, this means these `v<semver` releases contain all `op-*` components and exclude all `contracts-*` components.   `op-geth` embeds upstream geth’s version inside it’s own version as follows: `vMAJOR.GETH_MAJOR GETH_MINOR GETH_PATCH.PATCH`. Basically, geth’s version is our minor version. @@ -112,6 +113,7 @@ Note that we pad out to three characters for the geth minor version and two characters for the geth patch version. Since we cannot left-pad with zeroes, the geth major version is not padded.   See the [Node Software Releases](https://docs.optimism.io/builders/node-operators/releases) page of the documentation for more information about releases for the latest node components. + The full set of components that have releases are:   - `chain-mon`
diff --git OP/bedrock-devnet/devnet/__init__.py CELO/bedrock-devnet/devnet/__init__.py index 182172d3412d82b008589c61c873d05fa7cd10f3..bf7f69550e4785b98189abf06e1096265697d05c 100644 --- OP/bedrock-devnet/devnet/__init__.py +++ CELO/bedrock-devnet/devnet/__init__.py @@ -4,12 +4,10 @@ import os import subprocess import json import socket -import calendar import datetime import time import shutil import http.client -import gzip from multiprocessing import Process, Queue import concurrent.futures from collections import namedtuple @@ -34,6 +32,7 @@ DEVNET_NO_BUILD = os.getenv('DEVNET_NO_BUILD') == "true" DEVNET_L2OO = os.getenv('DEVNET_L2OO') == "true" DEVNET_PLASMA = os.getenv('DEVNET_PLASMA') == "true" GENERIC_PLASMA = os.getenv('GENERIC_PLASMA') == "true" +DEVNET_CELO = os.getenv('DEVNET_CELO') == "true"   class Bunch: def __init__(self, **kwds): @@ -139,13 +138,20 @@ if DEVNET_PLASMA: deploy_config['usePlasma'] = True if GENERIC_PLASMA: deploy_config['daCommitmentType'] = "GenericCommitment" + if DEVNET_CELO: + deploy_config['useFaultProofs'] = False + deploy_config['useCustomGasToken'] = True + deploy_config['deployCeloContracts'] = True + # Usage of the zero address in combination of the useCustomGasToken == True + # will deploy a new contract + deploy_config['customGasTokenAddress'] = "0x0000000000000000000000000000000000000000" write_json(paths.devnet_config_path, deploy_config)   def devnet_l1_allocs(paths): log.info('Generating L1 genesis allocs') init_devnet_l1_deploy_config(paths)   - fqn = 'scripts/Deploy.s.sol:Deploy' + fqn = 'scripts/deploy/Deploy.s.sol:Deploy' run_command([ # We need to set the sender here to an account we know the private key of, # because the sender ends up being the owner of the ProxyAdmin SAFE @@ -297,7 +303,7 @@ if not DEVNET_L2OO: log.info('Bringing up `op-challenger`.') run_command(['docker', 'compose', 'up', '-d', 'op-challenger'], cwd=paths.ops_bedrock_dir, env=docker_env)   - # Optionally bring up Plasma Mode components. + # Optionally bring up Alt-DA Mode components. if DEVNET_PLASMA: log.info('Bringing up `da-server`, `sentinel`.') # TODO(10141): We don't have public sentinel images yet run_command(['docker', 'compose', 'up', '-d', 'da-server'], cwd=paths.ops_bedrock_dir, env=docker_env)
diff --git OP/cannon/Makefile CELO/cannon/Makefile index 3accd2e11bc3ef34130158c44abe29c7910d647f..3be2e1b304e0d033c1d166ea96a9a0ec9406fc15 100644 --- OP/cannon/Makefile +++ CELO/cannon/Makefile @@ -30,7 +30,7 @@ go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallBrk ./mipsevm go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallClone ./mipsevm go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallMmap ./mipsevm go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExitGroup ./mipsevm - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFnctl ./mipsevm + go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFcntl ./mipsevm go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead ./mipsevm go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead ./mipsevm go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite ./mipsevm
diff --git OP/cannon/cmd/run.go CELO/cannon/cmd/run.go index 4c0970e3a99c1d158ba7e3e44ffda700d477ee71..f4d57a114d5aeca7ea1e8c1995b896ecf56dd020 100644 --- OP/cannon/cmd/run.go +++ CELO/cannon/cmd/run.go @@ -103,6 +103,12 @@ RunDebugFlag = &cli.BoolFlag{ Name: "debug", Usage: "enable debug mode, which includes stack traces and other debug info in the output. Requires --meta.", } + RunDebugInfoFlag = &cli.PathFlag{ + Name: "debug-info", + Usage: "path to write debug info to", + TakesFile: true, + Required: false, + }   OutFilePerm = os.FileMode(0o755) ) @@ -380,16 +386,16 @@ if infoAt(state) { delta := time.Since(start) l.Info("processing", "step", step, - "pc", mipsevm.HexU32(state.PC), - "insn", mipsevm.HexU32(state.Memory.GetMemory(state.PC)), + "pc", mipsevm.HexU32(state.Cpu.PC), + "insn", mipsevm.HexU32(state.Memory.GetMemory(state.Cpu.PC)), "ips", float64(step-startStep)/(float64(delta)/float64(time.Second)), "pages", state.Memory.PageCount(), "mem", state.Memory.Usage(), - "name", meta.LookupSymbol(state.PC), + "name", meta.LookupSymbol(state.Cpu.PC), ) }   - if sleepCheck(state.PC) { // don't loop forever when we get stuck because of an unexpected bad program + if sleepCheck(state.Cpu.PC) { // don't loop forever when we get stuck because of an unexpected bad program return fmt.Errorf("got stuck in Go sleep at step %d", step) }   @@ -411,7 +417,7 @@ return fmt.Errorf("failed to hash prestate witness: %w", err) } witness, err := stepFn(true) if err != nil { - return fmt.Errorf("failed at proof-gen step %d (PC: %08x): %w", step, state.PC, err) + return fmt.Errorf("failed at proof-gen step %d (PC: %08x): %w", step, state.Cpu.PC, err) } postStateHash, err := state.EncodeWitness().StateHash() if err != nil { @@ -435,7 +441,7 @@ } } else { _, err = stepFn(false) if err != nil { - return fmt.Errorf("failed at step %d (PC: %08x): %w", step, state.PC, err) + return fmt.Errorf("failed at step %d (PC: %08x): %w", step, state.Cpu.PC, err) } }   @@ -466,6 +472,11 @@ if err := jsonutil.WriteJSON(ctx.Path(RunOutputFlag.Name), state, OutFilePerm); err != nil { return fmt.Errorf("failed to write state output: %w", err) } + if debugInfoFile := ctx.Path(RunDebugInfoFlag.Name); debugInfoFile != "" { + if err := jsonutil.WriteJSON(debugInfoFile, us.GetDebugInfo(), OutFilePerm); err != nil { + return fmt.Errorf("failed to write benchmark data: %w", err) + } + } return nil }   @@ -489,5 +500,6 @@ RunMetaFlag, RunInfoAtFlag, RunPProfCPU, RunDebugFlag, + RunDebugInfoFlag, }, }
diff --git OP/cannon/docs/README.md CELO/cannon/docs/README.md index 90fbcd34dbd64f3ae8e65a2b23c1fea2a79182d1..61c9595e8f3c9347ecf25f2bc0dfcf51d4e845d7 100644 --- OP/cannon/docs/README.md +++ CELO/cannon/docs/README.md @@ -45,7 +45,7 @@ ### Packed State   The Packed State is provided in every executed onchain instruction. -See [Cannon VM Specs](https://github.com/ethereum-optimism/specs/blob/main/specs/experimental/fault-proof/cannon-fault-proof-vm.md#state) for +See [Cannon VM Specs](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/cannon-fault-proof-vm.md#state) for details on the state structure.   The packed state is small! The `State` data can be packed in such a small amount of EVM words,
diff --git OP/cannon/example/alloc/go.mod CELO/cannon/example/alloc/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..2f0739ca6c992a38757584d1e27ac96a4135f47e --- /dev/null +++ CELO/cannon/example/alloc/go.mod @@ -0,0 +1,14 @@ +module alloc + +go 1.21 + +toolchain go1.21.1 + +require github.com/ethereum-optimism/optimism v0.0.0 + +require ( + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/sys v0.21.0 // indirect +) + +replace github.com/ethereum-optimism/optimism v0.0.0 => ../../..
diff --git OP/cannon/example/alloc/go.sum CELO/cannon/example/alloc/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..e9147757ceeb84a3b77b4e3804f8157f8940484f --- /dev/null +++ CELO/cannon/example/alloc/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git OP/cannon/example/alloc/main.go CELO/cannon/example/alloc/main.go new file mode 100644 index 0000000000000000000000000000000000000000..41bc67000d2bbcb30e085430ea082a981585d005 --- /dev/null +++ CELO/cannon/example/alloc/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "encoding/binary" + "fmt" + "runtime" + + preimage "github.com/ethereum-optimism/optimism/op-preimage" +) + +func main() { + var mem []byte + po := preimage.NewOracleClient(preimage.ClientPreimageChannel()) + numAllocs := binary.LittleEndian.Uint64(po.Get(preimage.LocalIndexKey(0))) + + fmt.Printf("alloc program. numAllocs=%d\n", numAllocs) + var alloc int + for i := 0; i < int(numAllocs); i++ { + mem = make([]byte, 32*1024*1024) + alloc += len(mem) + // touch a couple pages to prevent the runtime from overcommitting memory + for j := 0; j < len(mem); j += 1024 { + mem[j] = 0xFF + } + fmt.Printf("allocated %d bytes\n", alloc) + } + + var m runtime.MemStats + runtime.ReadMemStats(&m) + fmt.Printf("alloc program exit. memstats: heap_alloc=%d frees=%d mallocs=%d\n", m.HeapAlloc, m.Frees, m.Mallocs) +}
diff --git OP/cannon/example/claim/main.go CELO/cannon/example/claim/main.go index ac22fa909040b604f83b4527def08e9d73a2e9d5..72b2064dec983919370ad046ed4ef6ed2bc6a2d2 100644 --- OP/cannon/example/claim/main.go +++ CELO/cannon/example/claim/main.go @@ -5,7 +5,7 @@ "encoding/binary" "fmt" "os"   - "github.com/ethereum-optimism/optimism/op-preimage" + preimage "github.com/ethereum-optimism/optimism/op-preimage" )   type rawHint string
diff --git OP/cannon/mipsevm/evm_test.go CELO/cannon/mipsevm/evm_test.go index 703dc6e30a61263203b672c679955b737dcbc7fe..14c53220c432e9fb3cab88f15ad603b8a11fd314 100644 --- OP/cannon/mipsevm/evm_test.go +++ CELO/cannon/mipsevm/evm_test.go @@ -172,7 +172,7 @@ fn := path.Join("open_mips_tests/test/bin", f.Name()) programMem, err := os.ReadFile(fn) require.NoError(t, err) - state := &State{PC: 0, NextPC: 4, Memory: NewMemory()} + state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()} err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem)) require.NoError(t, err, "load program into state")   @@ -182,14 +182,14 @@ goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)   for i := 0; i < 1000; i++ { - if goState.state.PC == endAddr { + if goState.state.Cpu.PC == endAddr { break } if exitGroup && goState.state.Exited { break } - insn := state.Memory.GetMemory(state.PC) - t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) + insn := state.Memory.GetMemory(state.Cpu.PC) + t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn)   stepWitness, err := goState.Step(true) require.NoError(t, err) @@ -201,11 +201,11 @@ require.Equalf(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), "mipsevm produced different state than EVM at step %d", state.Step) } if exitGroup { - require.NotEqual(t, uint32(endAddr), goState.state.PC, "must not reach end") + require.NotEqual(t, uint32(endAddr), goState.state.Cpu.PC, "must not reach end") require.True(t, goState.state.Exited, "must set exited state") require.Equal(t, uint8(1), goState.state.ExitCode, "must exit with 1") } else { - require.Equal(t, uint32(endAddr), state.PC, "must reach end") + require.Equal(t, uint32(endAddr), state.Cpu.PC, "must reach end") // inspect test result done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8) require.Equal(t, done, uint32(1), "must be done") @@ -233,7 +233,7 @@ }   for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - state := &State{PC: tt.pc, NextPC: tt.nextPC, Memory: NewMemory()} + state := &State{Cpu: CpuScalars{PC: tt.pc, NextPC: tt.nextPC}, Memory: NewMemory()} state.Memory.SetMemory(tt.pc, tt.insn)   us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr) @@ -401,7 +401,7 @@ for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { oracle := hintTrackingOracle{} - state := &State{PC: 0, NextPC: 4, Memory: NewMemory()} + state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()}   state.LastHint = tt.lastHint state.Registers[2] = sysWrite @@ -448,8 +448,8 @@ }   for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - state := &State{PC: 0, NextPC: tt.nextPC, Memory: NewMemory()} - initialState := &State{PC: 0, NextPC: tt.nextPC, Memory: state.Memory} + state := &State{Cpu: CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: NewMemory()} + initialState := &State{Cpu: CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: state.Memory} state.Memory.SetMemory(0, tt.insn)   // set the return address ($ra) to jump into when test completes @@ -496,9 +496,9 @@ for i := 0; i < 400_000; i++ { if goState.state.Exited { break } - insn := state.Memory.GetMemory(state.PC) + insn := state.Memory.GetMemory(state.Cpu.PC) if i%1000 == 0 { // avoid spamming test logs, we are executing many steps - t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) + t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn) }   evm := NewMIPSEVM(contracts, addrs) @@ -548,9 +548,9 @@ if goState.state.Exited { break }   - insn := state.Memory.GetMemory(state.PC) + insn := state.Memory.GetMemory(state.Cpu.PC) if i%1000 == 0 { // avoid spamming test logs, we are executing many steps - t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) + t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn) }   stepWitness, err := goState.Step(true)
diff --git OP/cannon/mipsevm/fuzz_evm_test.go CELO/cannon/mipsevm/fuzz_evm_test.go index 3ed3eefb7b38637fc16420b416047a04625a70b4..9b11f9363c931ae9a11798ba19db9680b1119784 100644 --- OP/cannon/mipsevm/fuzz_evm_test.go +++ CELO/cannon/mipsevm/fuzz_evm_test.go @@ -21,10 +21,12 @@ f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) { pc = pc & 0xFF_FF_FF_FC // align PC nextPC := pc + 4 state := &State{ - PC: pc, - NextPC: nextPC, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: pc, + NextPC: nextPC, + LO: 0, + HI: 0, + }, Heap: 0, ExitCode: 0, Exited: false, @@ -44,10 +46,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.False(t, stepWitness.HasPreimage())   - require.Equal(t, pc+4, state.PC) - require.Equal(t, nextPC+4, state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, pc+4, state.Cpu.PC) + require.Equal(t, nextPC+4, state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, false, state.Exited) @@ -71,10 +73,12 @@ f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) { pc = pc & 0xFF_FF_FF_FC // align PC nextPC := pc + 4 state := &State{ - PC: pc, - NextPC: nextPC, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: pc, + NextPC: nextPC, + LO: 0, + HI: 0, + }, Heap: 0, ExitCode: 0, Exited: false, @@ -93,10 +97,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.False(t, stepWitness.HasPreimage())   - require.Equal(t, pc+4, state.PC) - require.Equal(t, nextPC+4, state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, pc+4, state.Cpu.PC) + require.Equal(t, nextPC+4, state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, false, state.Exited) @@ -118,10 +122,12 @@ func FuzzStateSyscallMmap(f *testing.F) { contracts, addrs := testContractsSetup(f) f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32) { state := &State{ - PC: 0, - NextPC: 4, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: 0, + NextPC: 4, + LO: 0, + HI: 0, + }, Heap: heap, ExitCode: 0, Exited: false, @@ -139,10 +145,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.False(t, stepWitness.HasPreimage())   - require.Equal(t, uint32(4), state.PC) - require.Equal(t, uint32(8), state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, uint32(4), state.Cpu.PC) + require.Equal(t, uint32(8), state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, false, state.Exited) require.Equal(t, preStateRoot, state.Memory.MerkleRoot()) @@ -179,10 +185,12 @@ f.Fuzz(func(t *testing.T, exitCode uint8, pc uint32, step uint64) { pc = pc & 0xFF_FF_FF_FC // align PC nextPC := pc + 4 state := &State{ - PC: pc, - NextPC: nextPC, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: pc, + NextPC: nextPC, + LO: 0, + HI: 0, + }, Heap: 0, ExitCode: 0, Exited: false, @@ -200,10 +208,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.False(t, stepWitness.HasPreimage())   - require.Equal(t, pc, state.PC) - require.Equal(t, nextPC, state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, pc, state.Cpu.PC) + require.Equal(t, nextPC, state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint8(exitCode), state.ExitCode) require.Equal(t, true, state.Exited) @@ -221,14 +229,16 @@ "mipsevm produced different state than EVM") }) }   -func FuzzStateSyscallFnctl(f *testing.F) { +func FuzzStateSyscallFcntl(f *testing.F) { contracts, addrs := testContractsSetup(f) f.Fuzz(func(t *testing.T, fd uint32, cmd uint32) { state := &State{ - PC: 0, - NextPC: 4, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: 0, + NextPC: 4, + LO: 0, + HI: 0, + }, Heap: 0, ExitCode: 0, Exited: false, @@ -246,10 +256,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.False(t, stepWitness.HasPreimage())   - require.Equal(t, uint32(4), state.PC) - require.Equal(t, uint32(8), state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, uint32(4), state.Cpu.PC) + require.Equal(t, uint32(8), state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, false, state.Exited) @@ -289,10 +299,12 @@ contracts, addrs := testContractsSetup(f) f.Fuzz(func(t *testing.T, addr uint32, count uint32) { preimageData := []byte("hello world") state := &State{ - PC: 0, - NextPC: 4, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: 0, + NextPC: 4, + LO: 0, + HI: 0, + }, Heap: 0, ExitCode: 0, Exited: false, @@ -314,10 +326,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.False(t, stepWitness.HasPreimage())   - require.Equal(t, uint32(4), state.PC) - require.Equal(t, uint32(8), state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, uint32(4), state.Cpu.PC) + require.Equal(t, uint32(8), state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, false, state.Exited) @@ -342,10 +354,12 @@ if preimageOffset >= uint32(len(preimageData)) { t.SkipNow() } state := &State{ - PC: 0, - NextPC: 4, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: 0, + NextPC: 4, + LO: 0, + HI: 0, + }, Heap: 0, ExitCode: 0, Exited: false, @@ -372,10 +386,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.True(t, stepWitness.HasPreimage())   - require.Equal(t, uint32(4), state.PC) - require.Equal(t, uint32(8), state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, uint32(4), state.Cpu.PC) + require.Equal(t, uint32(8), state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, false, state.Exited) @@ -403,10 +417,12 @@ contracts, addrs := testContractsSetup(f) f.Fuzz(func(t *testing.T, addr uint32, count uint32, randSeed int64) { preimageData := []byte("hello world") state := &State{ - PC: 0, - NextPC: 4, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: 0, + NextPC: 4, + LO: 0, + HI: 0, + }, Heap: 0, ExitCode: 0, Exited: false, @@ -436,10 +452,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.False(t, stepWitness.HasPreimage())   - require.Equal(t, uint32(4), state.PC) - require.Equal(t, uint32(8), state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, uint32(4), state.Cpu.PC) + require.Equal(t, uint32(8), state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, false, state.Exited) @@ -461,10 +477,12 @@ contracts, addrs := testContractsSetup(f) f.Fuzz(func(t *testing.T, addr uint32, count uint32) { preimageData := []byte("hello world") state := &State{ - PC: 0, - NextPC: 4, - LO: 0, - HI: 0, + Cpu: CpuScalars{ + PC: 0, + NextPC: 4, + LO: 0, + HI: 0, + }, Heap: 0, ExitCode: 0, Exited: false, @@ -489,10 +507,10 @@ stepWitness, err := goState.Step(true) require.NoError(t, err) require.False(t, stepWitness.HasPreimage())   - require.Equal(t, uint32(4), state.PC) - require.Equal(t, uint32(8), state.NextPC) - require.Equal(t, uint32(0), state.LO) - require.Equal(t, uint32(0), state.HI) + require.Equal(t, uint32(4), state.Cpu.PC) + require.Equal(t, uint32(8), state.Cpu.NextPC) + require.Equal(t, uint32(0), state.Cpu.LO) + require.Equal(t, uint32(0), state.Cpu.HI) require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, false, state.Exited)
diff --git OP/cannon/mipsevm/instrumented.go CELO/cannon/mipsevm/instrumented.go index 6e1ac81f104ed97e7a3ec41d409b2f6783519166..011c7c4d06309f90b2a2686fd14b622a7464453a 100644 --- OP/cannon/mipsevm/instrumented.go +++ CELO/cannon/mipsevm/instrumented.go @@ -26,7 +26,7 @@ lastMemAccess uint32 memProofEnabled bool memProof [28 * 32]byte   - preimageOracle PreimageOracle + preimageOracle *trackingOracle   // cached pre-image data, including 8 byte length prefix lastPreimage []byte @@ -39,27 +39,12 @@ debug Debug debugEnabled bool }   -const ( - fdStdin = 0 - fdStdout = 1 - fdStderr = 2 - fdHintRead = 3 - fdHintWrite = 4 - fdPreimageRead = 5 - fdPreimageWrite = 6 -) - -const ( - MipsEBADF = 0x9 - MipsEINVAL = 0x16 -) - func NewInstrumentedState(state *State, po PreimageOracle, stdOut, stdErr io.Writer) *InstrumentedState { return &InstrumentedState{ state: state, stdOut: stdOut, stdErr: stdErr, - preimageOracle: po, + preimageOracle: &trackingOracle{po: po}, } }   @@ -78,7 +63,7 @@ m.lastMemAccess = ^uint32(0) m.lastPreimageOffset = ^uint32(0)   if proof { - insnProof := m.state.Memory.MerkleProof(m.state.PC) + insnProof := m.state.Memory.MerkleProof(m.state.Cpu.PC) wit = &StepWitness{ State: m.state.EncodeWitness(), MemProof: insnProof[:], @@ -103,3 +88,34 @@ func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, uint32) { return m.lastPreimageKey, m.lastPreimage, m.lastPreimageOffset } + +func (d *InstrumentedState) GetDebugInfo() *DebugInfo { + return &DebugInfo{ + Pages: d.state.Memory.PageCount(), + NumPreimageRequests: d.preimageOracle.numPreimageRequests, + TotalPreimageSize: d.preimageOracle.totalPreimageSize, + } +} + +type DebugInfo struct { + Pages int `json:"pages"` + NumPreimageRequests int `json:"num_preimage_requests"` + TotalPreimageSize int `json:"total_preimage_size"` +} + +type trackingOracle struct { + po PreimageOracle + totalPreimageSize int + numPreimageRequests int +} + +func (d *trackingOracle) Hint(v []byte) { + d.po.Hint(v) +} + +func (d *trackingOracle) GetPreimage(k [32]byte) []byte { + d.numPreimageRequests++ + preimage := d.po.GetPreimage(k) + d.totalPreimageSize += len(preimage) + return preimage +}
diff --git OP/cannon/mipsevm/mips.go CELO/cannon/mipsevm/mips.go index 9758c1fd75e505d3862b380057ccc58cd9c1e31f..e25bee13a275cc2290cad6e7156294b25ce5ebd0 100644 --- OP/cannon/mipsevm/mips.go +++ CELO/cannon/mipsevm/mips.go @@ -3,17 +3,9 @@ import ( "encoding/binary" "fmt" - "io" -)   -const ( - sysMmap = 4090 - sysBrk = 4045 - sysClone = 4120 - sysExitGroup = 4246 - sysRead = 4003 - sysWrite = 4004 - sysFcntl = 4055 + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" )   func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) { @@ -43,29 +35,17 @@ } }   func (m *InstrumentedState) handleSyscall() error { - syscallNum := m.state.Registers[2] // v0 + syscallNum, a0, a1, a2 := getSyscallArgs(&m.state.Registers) + v0 := uint32(0) v1 := uint32(0)   - a0 := m.state.Registers[4] - a1 := m.state.Registers[5] - a2 := m.state.Registers[6] - //fmt.Printf("syscall: %d\n", syscallNum) switch syscallNum { case sysMmap: - sz := a1 - if sz&PageAddrMask != 0 { // adjust size to align with page size - sz += PageSize - (sz & PageAddrMask) - } - if a0 == 0 { - v0 = m.state.Heap - //fmt.Printf("mmap heap 0x%x size 0x%x\n", v0, sz) - m.state.Heap += sz - } else { - v0 = a0 - //fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz) - } + var newHeap uint32 + v0, v1, newHeap = handleSysMmap(a0, a1, m.state.Heap) + m.state.Heap = newHeap case sysBrk: v0 = 0x40000000 case sysClone: // clone (not supported) @@ -75,107 +55,22 @@ m.state.Exited = true m.state.ExitCode = uint8(a0) return nil case sysRead: - // args: a0 = fd, a1 = addr, a2 = count - // returns: v0 = read, v1 = err code - switch a0 { - case fdStdin: - // leave v0 and v1 zero: read nothing, no error - case fdPreimageRead: // pre-image oracle - effAddr := a1 & 0xFFffFFfc - m.trackMemAccess(effAddr) - mem := m.state.Memory.GetMemory(effAddr) - dat, datLen := m.readPreimage(m.state.PreimageKey, m.state.PreimageOffset) - //fmt.Printf("reading pre-image data: addr: %08x, offset: %d, datLen: %d, data: %x, key: %s count: %d\n", a1, m.state.PreimageOffset, datLen, dat[:datLen], m.state.PreimageKey, a2) - alignment := a1 & 3 - space := 4 - alignment - if space < datLen { - datLen = space - } - if a2 < datLen { - datLen = a2 - } - var outMem [4]byte - binary.BigEndian.PutUint32(outMem[:], mem) - copy(outMem[alignment:], dat[:datLen]) - m.state.Memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:])) - m.state.PreimageOffset += datLen - v0 = datLen - //fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem) - case fdHintRead: // hint response - // don't actually read into memory, just say we read it all, we ignore the result anyway - v0 = a2 - default: - v0 = 0xFFffFFff - v1 = MipsEBADF - } + var newPreimageOffset uint32 + v0, v1, newPreimageOffset = handleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.readPreimage, m.state.Memory, m.trackMemAccess) + m.state.PreimageOffset = newPreimageOffset case sysWrite: - // args: a0 = fd, a1 = addr, a2 = count - // returns: v0 = written, v1 = err code - switch a0 { - case fdStdout: - _, _ = io.Copy(m.stdOut, m.state.Memory.ReadMemoryRange(a1, a2)) - v0 = a2 - case fdStderr: - _, _ = io.Copy(m.stdErr, m.state.Memory.ReadMemoryRange(a1, a2)) - v0 = a2 - case fdHintWrite: - hintData, _ := io.ReadAll(m.state.Memory.ReadMemoryRange(a1, a2)) - m.state.LastHint = append(m.state.LastHint, hintData...) - for len(m.state.LastHint) >= 4 { // process while there is enough data to check if there are any hints - hintLen := binary.BigEndian.Uint32(m.state.LastHint[:4]) - if hintLen <= uint32(len(m.state.LastHint[4:])) { - hint := m.state.LastHint[4 : 4+hintLen] // without the length prefix - m.state.LastHint = m.state.LastHint[4+hintLen:] - m.preimageOracle.Hint(hint) - } else { - break // stop processing hints if there is incomplete data buffered - } - } - v0 = a2 - case fdPreimageWrite: - effAddr := a1 & 0xFFffFFfc - m.trackMemAccess(effAddr) - mem := m.state.Memory.GetMemory(effAddr) - key := m.state.PreimageKey - alignment := a1 & 3 - space := 4 - alignment - if space < a2 { - a2 = space - } - copy(key[:], key[a2:]) - var tmp [4]byte - binary.BigEndian.PutUint32(tmp[:], mem) - copy(key[32-a2:], tmp[alignment:]) - m.state.PreimageKey = key - m.state.PreimageOffset = 0 - //fmt.Printf("updating pre-image key: %s\n", m.state.PreimageKey) - v0 = a2 - default: - v0 = 0xFFffFFff - v1 = MipsEBADF - } + var newLastHint hexutil.Bytes + var newPreimageKey common.Hash + var newPreimageOffset uint32 + v0, v1, newLastHint, newPreimageKey, newPreimageOffset = handleSysWrite(a0, a1, a2, m.state.LastHint, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.trackMemAccess, m.stdOut, m.stdErr) + m.state.LastHint = newLastHint + m.state.PreimageKey = newPreimageKey + m.state.PreimageOffset = newPreimageOffset case sysFcntl: - // args: a0 = fd, a1 = cmd - if a1 == 3 { // F_GETFL: get file descriptor flags - switch a0 { - case fdStdin, fdPreimageRead, fdHintRead: - v0 = 0 // O_RDONLY - case fdStdout, fdStderr, fdPreimageWrite, fdHintWrite: - v0 = 1 // O_WRONLY - default: - v0 = 0xFFffFFff - v1 = MipsEBADF - } - } else { - v0 = 0xFFffFFff - v1 = MipsEINVAL // cmd not recognized by this kernel - } + v0, v1 = handleSysFcntl(a0, a1) } - m.state.Registers[2] = v0 - m.state.Registers[7] = v1   - m.state.PC = m.state.NextPC - m.state.NextPC = m.state.NextPC + 4 + handleSyscallUpdates(&m.state.Cpu, &m.state.Registers, v0, v1) return nil }   @@ -184,7 +79,7 @@ if !m.debugEnabled { return } m.debug.stack = append(m.debug.stack, target) - m.debug.caller = append(m.debug.caller, m.state.PC) + m.debug.caller = append(m.debug.caller, m.state.Cpu.PC) }   func (m *InstrumentedState) popStack() { @@ -192,7 +87,7 @@ if !m.debugEnabled { return } if len(m.debug.stack) != 0 { - fn := m.debug.meta.LookupSymbol(m.state.PC) + fn := m.debug.meta.LookupSymbol(m.state.Cpu.PC) topFn := m.debug.meta.LookupSymbol(m.debug.stack[len(m.debug.stack)-1]) if fn != topFn { // most likely the function was inlined. Snap back to the last return. @@ -209,12 +104,12 @@ m.debug.stack = m.debug.stack[:len(m.debug.stack)-1] m.debug.caller = m.debug.caller[:len(m.debug.caller)-1] } } else { - fmt.Printf("ERROR: stack underflow at pc=%x. step=%d\n", m.state.PC, m.state.Step) + fmt.Printf("ERROR: stack underflow at pc=%x. step=%d\n", m.state.Cpu.PC, m.state.Step) } }   func (m *InstrumentedState) Traceback() { - fmt.Printf("traceback at pc=%x. step=%d\n", m.state.PC, m.state.Step) + fmt.Printf("traceback at pc=%x. step=%d\n", m.state.Cpu.PC, m.state.Step) for i := len(m.debug.stack) - 1; i >= 0; i-- { s := m.debug.stack[i] idx := len(m.debug.stack) - i - 1 @@ -222,108 +117,13 @@ fmt.Printf("\t%d %x in %s caller=%08x\n", idx, s, m.debug.meta.LookupSymbol(s), m.debug.caller[i]) } }   -func (m *InstrumentedState) handleBranch(opcode uint32, insn uint32, rtReg uint32, rs uint32) error { - if m.state.NextPC != m.state.PC+4 { - panic("branch in delay slot") - } - - shouldBranch := false - if opcode == 4 || opcode == 5 { // beq/bne - rt := m.state.Registers[rtReg] - shouldBranch = (rs == rt && opcode == 4) || (rs != rt && opcode == 5) - } else if opcode == 6 { - shouldBranch = int32(rs) <= 0 // blez - } else if opcode == 7 { - shouldBranch = int32(rs) > 0 // bgtz - } else if opcode == 1 { - // regimm - rtv := (insn >> 16) & 0x1F - if rtv == 0 { // bltz - shouldBranch = int32(rs) < 0 - } - if rtv == 1 { // bgez - shouldBranch = int32(rs) >= 0 - } - } - - prevPC := m.state.PC - m.state.PC = m.state.NextPC // execute the delay slot first - if shouldBranch { - m.state.NextPC = prevPC + 4 + (signExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to. - } else { - m.state.NextPC = m.state.NextPC + 4 // branch not taken - } - return nil -} - -func (m *InstrumentedState) handleHiLo(fun uint32, rs uint32, rt uint32, storeReg uint32) error { - val := uint32(0) - switch fun { - case 0x10: // mfhi - val = m.state.HI - case 0x11: // mthi - m.state.HI = rs - case 0x12: // mflo - val = m.state.LO - case 0x13: // mtlo - m.state.LO = rs - case 0x18: // mult - acc := uint64(int64(int32(rs)) * int64(int32(rt))) - m.state.HI = uint32(acc >> 32) - m.state.LO = uint32(acc) - case 0x19: // multu - acc := uint64(uint64(rs) * uint64(rt)) - m.state.HI = uint32(acc >> 32) - m.state.LO = uint32(acc) - case 0x1a: // div - m.state.HI = uint32(int32(rs) % int32(rt)) - m.state.LO = uint32(int32(rs) / int32(rt)) - case 0x1b: // divu - m.state.HI = rs % rt - m.state.LO = rs / rt - } - - if storeReg != 0 { - m.state.Registers[storeReg] = val - } - - m.state.PC = m.state.NextPC - m.state.NextPC = m.state.NextPC + 4 - return nil -} - -func (m *InstrumentedState) handleJump(linkReg uint32, dest uint32) error { - if m.state.NextPC != m.state.PC+4 { - panic("jump in delay slot") - } - prevPC := m.state.PC - m.state.PC = m.state.NextPC - m.state.NextPC = dest - if linkReg != 0 { - m.state.Registers[linkReg] = prevPC + 8 // set the link-register to the instr after the delay slot instruction. - } - return nil -} - -func (m *InstrumentedState) handleRd(storeReg uint32, val uint32, conditional bool) error { - if storeReg >= 32 { - panic("invalid register") - } - if storeReg != 0 && conditional { - m.state.Registers[storeReg] = val - } - m.state.PC = m.state.NextPC - m.state.NextPC = m.state.NextPC + 4 - return nil -} - func (m *InstrumentedState) mipsStep() error { if m.state.Exited { return nil } m.state.Step += 1 // instruction fetch - insn := m.state.Memory.GetMemory(m.state.PC) + insn := m.state.Memory.GetMemory(m.state.Cpu.PC) opcode := insn >> 26 // 6-bits   // j-type j/jal @@ -333,9 +133,9 @@ if opcode == 3 { linkReg = 31 } // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset - target := (m.state.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2) + target := (m.state.Cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2) m.pushStack(target) - return m.handleJump(linkReg, target) + return handleJump(&m.state.Cpu, &m.state.Registers, linkReg, target) }   // register fetch @@ -369,7 +169,7 @@ rdReg = rtReg }   if (opcode >= 4 && opcode < 8) || opcode == 1 { - return m.handleBranch(opcode, insn, rtReg, rs) + return handleBranch(&m.state.Cpu, &m.state.Registers, opcode, insn, rtReg, rs) }   storeAddr := uint32(0xFF_FF_FF_FF) @@ -401,14 +201,14 @@ if fun == 9 { linkReg = rdReg } m.popStack() - return m.handleJump(linkReg, rs) + return handleJump(&m.state.Cpu, &m.state.Registers, linkReg, rs) }   if fun == 0xa { // movz - return m.handleRd(rdReg, rs, rt == 0) + return handleRd(&m.state.Cpu, &m.state.Registers, rdReg, rs, rt == 0) } if fun == 0xb { // movn - return m.handleRd(rdReg, rs, rt != 0) + return handleRd(&m.state.Cpu, &m.state.Registers, rdReg, rs, rt != 0) }   // syscall (can read and write) @@ -419,7 +219,7 @@ // lo and hi registers // can write back if fun >= 0x10 && fun < 0x1c { - return m.handleHiLo(fun, rs, rt, rdReg) + return handleHiLo(&m.state.Cpu, &m.state.Registers, fun, rs, rt, rdReg) } }   @@ -435,5 +235,5 @@ m.state.Memory.SetMemory(storeAddr, val) }   // write back the value to destination register - return m.handleRd(rdReg, val, true) + return handleRd(&m.state.Cpu, &m.state.Registers, rdReg, val, true) }
diff --git OP/cannon/mipsevm/mips_instructions.go CELO/cannon/mipsevm/mips_instructions.go index cd3920d10eb5b47d41cd17f89a1cd96ad04a5f81..285ed26b6e1b3f7708210d2d2ae1e23850a091fe 100644 --- OP/cannon/mipsevm/mips_instructions.go +++ CELO/cannon/mipsevm/mips_instructions.go @@ -174,3 +174,98 @@ } else { return dat & mask } } + +func handleBranch(cpu *CpuScalars, registers *[32]uint32, opcode uint32, insn uint32, rtReg uint32, rs uint32) error { + if cpu.NextPC != cpu.PC+4 { + panic("branch in delay slot") + } + + shouldBranch := false + if opcode == 4 || opcode == 5 { // beq/bne + rt := registers[rtReg] + shouldBranch = (rs == rt && opcode == 4) || (rs != rt && opcode == 5) + } else if opcode == 6 { + shouldBranch = int32(rs) <= 0 // blez + } else if opcode == 7 { + shouldBranch = int32(rs) > 0 // bgtz + } else if opcode == 1 { + // regimm + rtv := (insn >> 16) & 0x1F + if rtv == 0 { // bltz + shouldBranch = int32(rs) < 0 + } + if rtv == 1 { // bgez + shouldBranch = int32(rs) >= 0 + } + } + + prevPC := cpu.PC + cpu.PC = cpu.NextPC // execute the delay slot first + if shouldBranch { + cpu.NextPC = prevPC + 4 + (signExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to. + } else { + cpu.NextPC = cpu.NextPC + 4 // branch not taken + } + return nil +} + +func handleHiLo(cpu *CpuScalars, registers *[32]uint32, fun uint32, rs uint32, rt uint32, storeReg uint32) error { + val := uint32(0) + switch fun { + case 0x10: // mfhi + val = cpu.HI + case 0x11: // mthi + cpu.HI = rs + case 0x12: // mflo + val = cpu.LO + case 0x13: // mtlo + cpu.LO = rs + case 0x18: // mult + acc := uint64(int64(int32(rs)) * int64(int32(rt))) + cpu.HI = uint32(acc >> 32) + cpu.LO = uint32(acc) + case 0x19: // multu + acc := uint64(uint64(rs) * uint64(rt)) + cpu.HI = uint32(acc >> 32) + cpu.LO = uint32(acc) + case 0x1a: // div + cpu.HI = uint32(int32(rs) % int32(rt)) + cpu.LO = uint32(int32(rs) / int32(rt)) + case 0x1b: // divu + cpu.HI = rs % rt + cpu.LO = rs / rt + } + + if storeReg != 0 { + registers[storeReg] = val + } + + cpu.PC = cpu.NextPC + cpu.NextPC = cpu.NextPC + 4 + return nil +} + +func handleJump(cpu *CpuScalars, registers *[32]uint32, linkReg uint32, dest uint32) error { + if cpu.NextPC != cpu.PC+4 { + panic("jump in delay slot") + } + prevPC := cpu.PC + cpu.PC = cpu.NextPC + cpu.NextPC = dest + if linkReg != 0 { + registers[linkReg] = prevPC + 8 // set the link-register to the instr after the delay slot instruction. + } + return nil +} + +func handleRd(cpu *CpuScalars, registers *[32]uint32, storeReg uint32, val uint32, conditional bool) error { + if storeReg >= 32 { + panic("invalid register") + } + if storeReg != 0 && conditional { + registers[storeReg] = val + } + cpu.PC = cpu.NextPC + cpu.NextPC = cpu.NextPC + 4 + return nil +}
diff --git OP/cannon/mipsevm/mips_syscalls.go CELO/cannon/mipsevm/mips_syscalls.go new file mode 100644 index 0000000000000000000000000000000000000000..098cf0cd7cfad28128dd889ee1a1648ea155af12 --- /dev/null +++ CELO/cannon/mipsevm/mips_syscalls.go @@ -0,0 +1,195 @@ +package mipsevm + +import ( + "encoding/binary" + "io" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +const ( + sysMmap = 4090 + sysBrk = 4045 + sysClone = 4120 + sysExitGroup = 4246 + sysRead = 4003 + sysWrite = 4004 + sysFcntl = 4055 +) + +const ( + fdStdin = 0 + fdStdout = 1 + fdStderr = 2 + fdHintRead = 3 + fdHintWrite = 4 + fdPreimageRead = 5 + fdPreimageWrite = 6 +) + +const ( + MipsEBADF = 0x9 + MipsEINVAL = 0x16 +) + +type PreimageReader func(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) +type MemTracker func(addr uint32) + +func getSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) { + syscallNum = registers[2] // v0 + + a0 = registers[4] + a1 = registers[5] + a2 = registers[6] + + return syscallNum, a0, a1, a2 +} + +func handleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) { + v1 = uint32(0) + newHeap = heap + + sz := a1 + if sz&PageAddrMask != 0 { // adjust size to align with page size + sz += PageSize - (sz & PageAddrMask) + } + if a0 == 0 { + v0 = heap + //fmt.Printf("mmap heap 0x%x size 0x%x\n", v0, sz) + newHeap += sz + } else { + v0 = a0 + //fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz) + } + + return v0, v1, newHeap +} + +func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *Memory, memTracker MemTracker) (v0, v1, newPreimageOffset uint32) { + // args: a0 = fd, a1 = addr, a2 = count + // returns: v0 = read, v1 = err code + v0 = uint32(0) + v1 = uint32(0) + newPreimageOffset = preimageOffset + + switch a0 { + case fdStdin: + // leave v0 and v1 zero: read nothing, no error + case fdPreimageRead: // pre-image oracle + effAddr := a1 & 0xFFffFFfc + memTracker(effAddr) + mem := memory.GetMemory(effAddr) + dat, datLen := preimageReader(preimageKey, preimageOffset) + //fmt.Printf("reading pre-image data: addr: %08x, offset: %d, datLen: %d, data: %x, key: %s count: %d\n", a1, m.state.PreimageOffset, datLen, dat[:datLen], m.state.PreimageKey, a2) + alignment := a1 & 3 + space := 4 - alignment + if space < datLen { + datLen = space + } + if a2 < datLen { + datLen = a2 + } + var outMem [4]byte + binary.BigEndian.PutUint32(outMem[:], mem) + copy(outMem[alignment:], dat[:datLen]) + memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:])) + newPreimageOffset += datLen + v0 = datLen + //fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem) + case fdHintRead: // hint response + // don't actually read into memory, just say we read it all, we ignore the result anyway + v0 = a2 + default: + v0 = 0xFFffFFff + v1 = MipsEBADF + } + + return v0, v1, newPreimageOffset +} + +func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle PreimageOracle, memory *Memory, memTracker MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) { + // args: a0 = fd, a1 = addr, a2 = count + // returns: v0 = written, v1 = err code + v1 = uint32(0) + newLastHint = lastHint + newPreimageKey = preimageKey + newPreimageOffset = preimageOffset + + switch a0 { + case fdStdout: + _, _ = io.Copy(stdOut, memory.ReadMemoryRange(a1, a2)) + v0 = a2 + case fdStderr: + _, _ = io.Copy(stdErr, memory.ReadMemoryRange(a1, a2)) + v0 = a2 + case fdHintWrite: + hintData, _ := io.ReadAll(memory.ReadMemoryRange(a1, a2)) + lastHint = append(lastHint, hintData...) + for len(lastHint) >= 4 { // process while there is enough data to check if there are any hints + hintLen := binary.BigEndian.Uint32(lastHint[:4]) + if hintLen <= uint32(len(lastHint[4:])) { + hint := lastHint[4 : 4+hintLen] // without the length prefix + lastHint = lastHint[4+hintLen:] + oracle.Hint(hint) + } else { + break // stop processing hints if there is incomplete data buffered + } + } + newLastHint = lastHint + v0 = a2 + case fdPreimageWrite: + effAddr := a1 & 0xFFffFFfc + memTracker(effAddr) + mem := memory.GetMemory(effAddr) + key := preimageKey + alignment := a1 & 3 + space := 4 - alignment + if space < a2 { + a2 = space + } + copy(key[:], key[a2:]) + var tmp [4]byte + binary.BigEndian.PutUint32(tmp[:], mem) + copy(key[32-a2:], tmp[alignment:]) + newPreimageKey = key + newPreimageOffset = 0 + //fmt.Printf("updating pre-image key: %s\n", m.state.PreimageKey) + v0 = a2 + default: + v0 = 0xFFffFFff + v1 = MipsEBADF + } + + return v0, v1, newLastHint, newPreimageKey, newPreimageOffset +} + +func handleSysFcntl(a0, a1 uint32) (v0, v1 uint32) { + // args: a0 = fd, a1 = cmd + v1 = uint32(0) + + if a1 == 3 { // F_GETFL: get file descriptor flags + switch a0 { + case fdStdin, fdPreimageRead, fdHintRead: + v0 = 0 // O_RDONLY + case fdStdout, fdStderr, fdPreimageWrite, fdHintWrite: + v0 = 1 // O_WRONLY + default: + v0 = 0xFFffFFff + v1 = MipsEBADF + } + } else { + v0 = 0xFFffFFff + v1 = MipsEINVAL // cmd not recognized by this kernel + } + + return v0, v1 +} + +func handleSyscallUpdates(cpu *CpuScalars, registers *[32]uint32, v0, v1 uint32) { + registers[2] = v0 + registers[7] = v1 + + cpu.PC = cpu.NextPC + cpu.NextPC = cpu.NextPC + 4 +}
diff --git OP/cannon/mipsevm/open_mips_tests/test/fcntl.asm CELO/cannon/mipsevm/open_mips_tests/test/fcntl.asm index 451e90a117884ddc7d2eef98419cb79a381cfea2..5f597bbe0c43319b48c6b2d9da4d39c0e6cc87fe 100644 --- OP/cannon/mipsevm/open_mips_tests/test/fcntl.asm +++ CELO/cannon/mipsevm/open_mips_tests/test/fcntl.asm @@ -5,7 +5,7 @@ .global test .ent test   test: - # fnctl(0, 3) + # fcntl(0, 3) li $v0, 4055 li $a0, 0x0 li $a1, 0x3
diff --git OP/cannon/mipsevm/patch.go CELO/cannon/mipsevm/patch.go index 64a05e9611a45bc6740c51a8033afe547b830674..47abb41e0915919afee71eea5d9b3659a50f1b63 100644 --- OP/cannon/mipsevm/patch.go +++ CELO/cannon/mipsevm/patch.go @@ -12,10 +12,12 @@ const HEAP_START = 0x05000000   func LoadELF(f *elf.File) (*State, error) { s := &State{ - PC: uint32(f.Entry), - NextPC: uint32(f.Entry + 4), - HI: 0, - LO: 0, + Cpu: CpuScalars{ + PC: uint32(f.Entry), + NextPC: uint32(f.Entry + 4), + LO: 0, + HI: 0, + }, Heap: HEAP_START, Registers: [32]uint32{}, Memory: NewMemory(),
diff --git OP/cannon/mipsevm/state.go CELO/cannon/mipsevm/state.go index d8a5dcfe9ae258bae62fa66e58c01f5db8d64ad4..c8a681f23e37eb9436722f89e4c454fa8a115f13 100644 --- OP/cannon/mipsevm/state.go +++ CELO/cannon/mipsevm/state.go @@ -2,6 +2,7 @@ package mipsevm   import ( "encoding/binary" + "encoding/json" "fmt"   "github.com/ethereum/go-ethereum/common" @@ -12,17 +13,22 @@ // StateWitnessSize is the size of the state witness encoding in bytes. var StateWitnessSize = 226   +type CpuScalars struct { + PC uint32 `json:"pc"` + NextPC uint32 `json:"nextPC"` + LO uint32 `json:"lo"` + HI uint32 `json:"hi"` +} + type State struct { Memory *Memory `json:"memory"`   PreimageKey common.Hash `json:"preimageKey"` PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix   - PC uint32 `json:"pc"` - NextPC uint32 `json:"nextPC"` - LO uint32 `json:"lo"` - HI uint32 `json:"hi"` - Heap uint32 `json:"heap"` // to handle mmap growth + Cpu CpuScalars `json:"cpu"` + + Heap uint32 `json:"heap"` // to handle mmap growth   ExitCode uint8 `json:"exit"` Exited bool `json:"exited"` @@ -42,6 +48,62 @@ // and should only be read when len(LastHint) > 4 && uint32(LastHint[:4]) <= len(LastHint[4:]) LastHint hexutil.Bytes `json:"lastHint,omitempty"` }   +type stateMarshaling struct { + Memory *Memory `json:"memory"` + PreimageKey common.Hash `json:"preimageKey"` + PreimageOffset uint32 `json:"preimageOffset"` + PC uint32 `json:"pc"` + NextPC uint32 `json:"nextPC"` + LO uint32 `json:"lo"` + HI uint32 `json:"hi"` + Heap uint32 `json:"heap"` + ExitCode uint8 `json:"exit"` + Exited bool `json:"exited"` + Step uint64 `json:"step"` + Registers [32]uint32 `json:"registers"` + LastHint hexutil.Bytes `json:"lastHint,omitempty"` +} + +func (s *State) MarshalJSON() ([]byte, error) { // nosemgrep + sm := &stateMarshaling{ + Memory: s.Memory, + PreimageKey: s.PreimageKey, + PreimageOffset: s.PreimageOffset, + PC: s.Cpu.PC, + NextPC: s.Cpu.NextPC, + LO: s.Cpu.LO, + HI: s.Cpu.HI, + Heap: s.Heap, + ExitCode: s.ExitCode, + Exited: s.Exited, + Step: s.Step, + Registers: s.Registers, + LastHint: s.LastHint, + } + return json.Marshal(sm) +} + +func (s *State) UnmarshalJSON(data []byte) error { + sm := new(stateMarshaling) + if err := json.Unmarshal(data, sm); err != nil { + return err + } + s.Memory = sm.Memory + s.PreimageKey = sm.PreimageKey + s.PreimageOffset = sm.PreimageOffset + s.Cpu.PC = sm.PC + s.Cpu.NextPC = sm.NextPC + s.Cpu.LO = sm.LO + s.Cpu.HI = sm.HI + s.Heap = sm.Heap + s.ExitCode = sm.ExitCode + s.Exited = sm.Exited + s.Step = sm.Step + s.Registers = sm.Registers + s.LastHint = sm.LastHint + return nil +} + func (s *State) GetStep() uint64 { return s.Step }   func (s *State) VMStatus() uint8 { @@ -54,10 +116,10 @@ memRoot := s.Memory.MerkleRoot() out = append(out, memRoot[:]...) out = append(out, s.PreimageKey[:]...) out = binary.BigEndian.AppendUint32(out, s.PreimageOffset) - out = binary.BigEndian.AppendUint32(out, s.PC) - out = binary.BigEndian.AppendUint32(out, s.NextPC) - out = binary.BigEndian.AppendUint32(out, s.LO) - out = binary.BigEndian.AppendUint32(out, s.HI) + out = binary.BigEndian.AppendUint32(out, s.Cpu.PC) + out = binary.BigEndian.AppendUint32(out, s.Cpu.NextPC) + out = binary.BigEndian.AppendUint32(out, s.Cpu.LO) + out = binary.BigEndian.AppendUint32(out, s.Cpu.HI) out = binary.BigEndian.AppendUint32(out, s.Heap) out = append(out, s.ExitCode) if s.Exited {
diff --git OP/cannon/mipsevm/state_test.go CELO/cannon/mipsevm/state_test.go index b430398c389b1c8405955c49a9ac3b324b308fea..dfb90674ec78ae902fcc339e2988ecc83bc2cf8d 100644 --- OP/cannon/mipsevm/state_test.go +++ CELO/cannon/mipsevm/state_test.go @@ -44,7 +44,7 @@ //state, err := LoadELF(elfProgram) //require.NoError(t, err, "must load ELF into state") programMem, err := os.ReadFile(fn) require.NoError(t, err) - state := &State{PC: 0, NextPC: 4, Memory: NewMemory()} + state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()} err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem)) require.NoError(t, err, "load program into state")   @@ -54,7 +54,7 @@ us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)   for i := 0; i < 1000; i++ { - if us.state.PC == endAddr { + if us.state.Cpu.PC == endAddr { break } if exitGroup && us.state.Exited { @@ -65,11 +65,11 @@ require.NoError(t, err) }   if exitGroup { - require.NotEqual(t, uint32(endAddr), us.state.PC, "must not reach end") + require.NotEqual(t, uint32(endAddr), us.state.Cpu.PC, "must not reach end") require.True(t, us.state.Exited, "must set exited state") require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1") } else { - require.Equal(t, uint32(endAddr), us.state.PC, "must reach end") + require.Equal(t, uint32(endAddr), us.state.Cpu.PC, "must reach end") done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8) // inspect test result require.Equal(t, done, uint32(1), "must be done") @@ -127,15 +127,7 @@ } }   func TestHello(t *testing.T) { - elfProgram, err := elf.Open("../example/bin/hello.elf") - require.NoError(t, err, "open ELF file") - - state, err := LoadELF(elfProgram) - require.NoError(t, err, "load ELF into state") - - err = PatchGo(elfProgram, state) - require.NoError(t, err, "apply Go runtime patches") - require.NoError(t, PatchStack(state), "add initial stack") + state := loadELFProgram(t, "../example/bin/hello.elf")   var stdOutBuf, stdErrBuf bytes.Buffer us := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr)) @@ -225,15 +217,7 @@ return oracle, fmt.Sprintf("computing %d * %d + %d\nclaim %d is good!\n", s, a, b, s*a+b), "started!" }   func TestClaim(t *testing.T) { - elfProgram, err := elf.Open("../example/bin/claim.elf") - require.NoError(t, err, "open ELF file") - - state, err := LoadELF(elfProgram) - require.NoError(t, err, "load ELF into state") - - err = PatchGo(elfProgram, state) - require.NoError(t, err, "apply Go runtime patches") - require.NoError(t, PatchStack(state), "add initial stack") + state := loadELFProgram(t, "../example/bin/claim.elf")   oracle, expectedStdOut, expectedStdErr := claimTestOracle(t)   @@ -255,6 +239,44 @@ require.Equal(t, expectedStdOut, stdOutBuf.String(), "stdout") require.Equal(t, expectedStdErr, stdErrBuf.String(), "stderr") }   +func TestAlloc(t *testing.T) { + t.Skip("TODO(client-pod#906): Currently fails on Single threaded Cannon. Re-enable for the MT FPVM") + + state := loadELFProgram(t, "../example/bin/alloc.elf") + const numAllocs = 100 // where each alloc is a 32 MiB chunk + oracle := allocOracle(t, numAllocs) + + // completes in ~870 M steps + us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr) + for i := 0; i < 20_000_000_000; i++ { + if us.state.Exited { + break + } + _, err := us.Step(false) + require.NoError(t, err) + if state.Step%10_000_000 == 0 { + t.Logf("Completed %d steps", state.Step) + } + } + t.Logf("Completed in %d steps", state.Step) + require.True(t, state.Exited, "must complete program") + require.Equal(t, uint8(0), state.ExitCode, "exit with 0") + require.Less(t, state.Memory.PageCount()*PageSize, 1*1024*1024*1024, "must not allocate more than 1 GiB") +} + +func loadELFProgram(t *testing.T, name string) *State { + elfProgram, err := elf.Open(name) + require.NoError(t, err, "open ELF file") + + state, err := LoadELF(elfProgram) + require.NoError(t, err, "load ELF into state") + + err = PatchGo(elfProgram, state) + require.NoError(t, err, "apply Go runtime patches") + require.NoError(t, PatchStack(state), "add initial stack") + return state +} + func staticOracle(t *testing.T, preimageData []byte) *testOracle { return &testOracle{ hint: func(v []byte) {}, @@ -289,6 +311,18 @@ }, } }   +func allocOracle(t *testing.T, numAllocs int) *testOracle { + return &testOracle{ + hint: func(v []byte) {}, + getPreimage: func(k [32]byte) []byte { + if k != preimage.LocalIndexKey(0).PreimageKey() { + t.Fatalf("invalid preimage request for %x", k) + } + return binary.LittleEndian.AppendUint64(nil, uint64(numAllocs)) + }, + } +} + func selectOracleFixture(t *testing.T, programName string) PreimageOracle { if strings.HasPrefix(programName, "oracle_kzg") { precompile := common.BytesToAddress([]byte{0xa}) @@ -301,3 +335,26 @@ } else { return nil } } + +func TestStateJSONCodec(t *testing.T) { + elfProgram, err := elf.Open("../example/bin/hello.elf") + require.NoError(t, err, "open ELF file") + state, err := LoadELF(elfProgram) + require.NoError(t, err, "load ELF into state") + + stateJSON, err := state.MarshalJSON() + require.NoError(t, err) + + newState := new(State) + require.NoError(t, newState.UnmarshalJSON(stateJSON)) + + require.Equal(t, state.PreimageKey, newState.PreimageKey) + require.Equal(t, state.PreimageOffset, newState.PreimageOffset) + require.Equal(t, state.Cpu, newState.Cpu) + require.Equal(t, state.Heap, newState.Heap) + require.Equal(t, state.ExitCode, newState.ExitCode) + require.Equal(t, state.Exited, newState.Exited) + require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot()) + require.Equal(t, state.Registers, newState.Registers) + require.Equal(t, state.Step, newState.Step) +}
diff --git OP/docker-bake.hcl CELO/docker-bake.hcl index 977aaf2ddbf95367b536285885aaa82e43afcf52..3fe9fabaf1727ae04b7697b69c925b236e068ebe 100644 --- OP/docker-bake.hcl +++ CELO/docker-bake.hcl @@ -61,6 +61,10 @@ variable "OP_PROGRAM_VERSION" { default = "${GIT_VERSION}" }   +variable "OP_SUPERVISOR_VERSION" { + default = "${GIT_VERSION}" +} + variable "CANNON_VERSION" { default = "${GIT_VERSION}" } @@ -186,30 +190,30 @@ platforms = split(",", PLATFORMS) tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/op-program:${tag}"] }   -target "cannon" { +target "op-supervisor" { dockerfile = "ops/docker/op-stack-go/Dockerfile" context = "." args = { GIT_COMMIT = "${GIT_COMMIT}" GIT_DATE = "${GIT_DATE}" - CANNON_VERSION = "${CANNON_VERSION}" + OP_SUPERVISOR_VERSION = "${OP_SUPERVISOR_VERSION}" } - target = "cannon-target" + target = "op-supervisor-target" platforms = split(",", PLATFORMS) - tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/cannon:${tag}"] + tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/op-supervisor:${tag}"] }   -target "proxyd" { - dockerfile = "./proxyd/Dockerfile" - context = "./" +target "cannon" { + dockerfile = "ops/docker/op-stack-go/Dockerfile" + context = "." args = { - // proxyd dockerfile has no _ in the args - GITCOMMIT = "${GIT_COMMIT}" - GITDATE = "${GIT_DATE}" - GITVERSION = "${GIT_VERSION}" + GIT_COMMIT = "${GIT_COMMIT}" + GIT_DATE = "${GIT_DATE}" + CANNON_VERSION = "${CANNON_VERSION}" } + target = "cannon-target" platforms = split(",", PLATFORMS) - tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/proxyd:${tag}"] + tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/cannon:${tag}"] }   target "chain-mon" {
diff --git OP/docs/fault-proof-alpha/README.md CELO/docs/fault-proof-alpha/README.md index f46e51ba7dd2650af174e433d578111b6475602b..0e647cf60d9dfe12985e551d13503c27fdf03437 100644 --- OP/docs/fault-proof-alpha/README.md +++ CELO/docs/fault-proof-alpha/README.md @@ -16,10 +16,10 @@ ### Contents   * Specifications - * [Generic Fault Proof System](../../specs/fault-proof.md) - * [Generic Dispute Game Interface](../../specs/dispute-game-interface.md) - * [Fault Dispute Game](../../specs/fault-dispute-game.md) - * [Cannon VM](../../specs/cannon-fault-proof-vm.md) + * [Generic Fault Proof System](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/index.md) + * [Generic Dispute Game Interface](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/stage-one/dispute-game-interface.md) + * [Fault Dispute Game](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/stage-one/fault-dispute-game.md) + * [Cannon VM](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/cannon-fault-proof-vm.md) * [Deployment Details](./deployments.md) * [Manual Usage](./manual.md) * [Creating Traces with Cannon](./cannon.md)
diff --git OP/docs/fault-proof-alpha/immunefi.md CELO/docs/fault-proof-alpha/immunefi.md index f3ad942950b02f30486c1cfc0c4b184513fd8e0d..a8becf158f5f8bc560f980dc1a60890a5bdbadc9 100644 --- OP/docs/fault-proof-alpha/immunefi.md +++ CELO/docs/fault-proof-alpha/immunefi.md @@ -88,13 +88,13 @@ See our bounty program on [Immunefi][immunefi] for information regarding reward sizes.   <!-- LINKS --> [cannon]: https://github.com/ethereum-optimism/optimism/tree/develop/cannon -[cannon-vm-specs]: https://github.com/ethereum-optimism/optimism/blob/develop/specs/cannon-fault-proof-vm.md +[cannon-vm-specs]: https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/cannon-fault-proof-vm.md [dispute-game]: https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/dispute -[fault-dispute-specs]: https://github.com/ethereum-optimism/optimism/blob/develop/specs/fault-dispute-game.md +[fault-dispute-specs]: https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/stage-one/fault-dispute-game.md [cannon-contracts]: https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/cannon [op-program]: https://github.com/ethereum-optimism/optimism/tree/develop/op-program [op-challenger]: https://github.com/ethereum-optimism/optimism/tree/develop/op-challenger [alphabet-vm]: https://github.com/ethereum-optimism/optimism/blob/c1cbacef0097c28f999e3655200e6bd0d4dba9f2/packages/contracts-bedrock/test/FaultDisputeGame.t.sol#L977-L1005 -[fault-proof-specs]: https://github.com/ethereum-optimism/optimism/blob/develop/specs/fault-proof.md +[fault-proof-specs]: https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/index.md [immunefi]: https://immunefi.com/bounty/optimism/ [invalid-proposal-doc]: https://github.com/ethereum-optimism/optimism/blob/develop/docs/fault-proof-alpha/invalid-proposals.md
diff --git OP/docs/fault-proof-alpha/manual.md CELO/docs/fault-proof-alpha/manual.md index 36580defa0d69433f436c8a8b0ccbf8100740671..0963cdf44f94be51d297f503597a926af6961732 100644 --- OP/docs/fault-proof-alpha/manual.md +++ CELO/docs/fault-proof-alpha/manual.md @@ -30,7 +30,7 @@ arbitrary hash can be used for claim values. For more advanced cases [cannon can be used](./cannon.md) to generate a trace, including the claim values to use at specific steps. Note that it is not valid to create a game that disputes an output root, using the final hash from a trace that confirms the output root is valid. To dispute an output root successfully, the trace must resolve that the disputed output root is invalid. This is indicated by the first byte of -the claim value being set to the invalid [VM status](../../specs/cannon-fault-proof-vm.md#state-hash) (`0x01`). +the claim value being set to the invalid [VM status](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/cannon-fault-proof-vm.md#state-hash) (`0x01`).   The game can then be created by calling the `create` method on the `DisputeGameFactory` contract. This requires three parameters: @@ -94,9 +94,9 @@ #### Populating the Pre-image Oracle   When the instruction to be executed as part of a `step` call reads from some pre-image data, that data must be loaded into the pre-image oracle prior to calling `step`. -For [local pre-image keys](../../specs/fault-proof.md#type-1-local-key), the pre-image must be populated via +For [local pre-image keys](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/index.md#type-1-local-key), the pre-image must be populated via the `FaultDisputeGame` contract by calling the `addLocalData` function. -For [global keccak256 keys](../../specs/fault-proof.md#type-2-global-keccak256-key), the data should be added directly +For [global keccak256 keys](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/index.md#type-2-global-keccak256-key), the data should be added directly to the pre-image oracle contract.   ### Resolving a Game
diff --git OP/docs/postmortems/2022-02-02-inflation-vuln.md CELO/docs/postmortems/2022-02-02-inflation-vuln.md index 11574eed70c2c75b8fa06e13745ffb380628dea7..a755b0fdfe38f07bdf15eda8e9d5580a2bf1d588 100644 --- OP/docs/postmortems/2022-02-02-inflation-vuln.md +++ CELO/docs/postmortems/2022-02-02-inflation-vuln.md @@ -5,7 +5,7 @@ It also details our response, lessons learned, and subsequent changes to our processes.   ## Incident Summary   -A vulnerability in Optimism’s fork of [Geth](https://github.com/ethereum/go-ethereum) (which we refer to as [L2Geth](../../l2geth/README.md)) was reported +A vulnerability in Optimism’s fork of [Geth](https://github.com/ethereum/go-ethereum) (which we refer to as [L2Geth](https://github.com/ethereum-optimism/optimism-legacy/blob/8205f678b7b4ac4625c2afe351b9c82ffaa2e795/l2geth/README.md)) was reported to us by [Jay Freeman](https://twitter.com/saurik) (AKA saurik) on February 2nd, 2022. If exploited, this vulnerability would allow anyone to mint an unbounded amount of ETH on Optimism.   @@ -133,7 +133,7 @@ the PR (36,311 lines added, 47,430 lines removed), which consumed the attention of our entire engineering team with a sense of urgency for several months.   An additional factor contributing to this bug was the significant complexity of the -[L2Geth](https://github.com/ethereum-optimism/optimism/tree/master/l2geth) codebase, which is a fork +[L2Geth](https://github.com/ethereum-optimism/optimism-legacy/blob/8205f678b7b4ac4625c2afe351b9c82ffaa2e795/l2geth) codebase, which is a fork of [Geth](https://github.com/ethereum/go-ethereum). Geth itself is already a very complex codebase. The changes introduced to L2Geth in order to support the OVM made it much more complex, such that very few people properly understood how it worked.
diff --git OP/docs/security-reviews/2024_05-FaultProofs-Sherlock.pdf CELO/docs/security-reviews/2024_05-FaultProofs-Sherlock.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ba6da27b4b26446123f40e618a0aba5f83cf7446 Binary files /dev/null and CELO/docs/security-reviews/2024_05-FaultProofs-Sherlock.pdf differ
diff --git OP/op-batcher/batcher/channel_builder.go CELO/op-batcher/batcher/channel_builder.go index 1a4ed28bb3f380ed0283cfc7bdce22622c0187e4..9e496b290d9254ba5147a8e0650189790c142b9b 100644 --- OP/op-batcher/batcher/channel_builder.go +++ CELO/op-batcher/batcher/channel_builder.go @@ -82,7 +82,7 @@ // total amount of output data of all frames created yet outputBytes int }   -// newChannelBuilder creates a new channel builder or returns an error if the +// NewChannelBuilder creates a new channel builder or returns an error if the // channel out could not be created. // it acts as a factory for either a span or singular channel out func NewChannelBuilder(cfg ChannelConfig, rollupCfg rollup.Config, latestL1OriginBlockNum uint64) (*ChannelBuilder, error) {
diff --git OP/op-batcher/batcher/driver.go CELO/op-batcher/batcher/driver.go index 8d8e73bee2b4ae673736c4085ba25677a4a2e2a5..b01bb51484c7bcd64cd0963700977ca4320b5747 100644 --- OP/op-batcher/batcher/driver.go +++ CELO/op-batcher/batcher/driver.go @@ -167,7 +167,7 @@ l.Log.Warn("Found L2 reorg", "block_number", i) l.lastStoredBlock = eth.BlockID{} return err } else if err != nil { - l.Log.Warn("failed to load block into state", "err", err) + l.Log.Warn("Failed to load block into state", "err", err) return err } l.lastStoredBlock = eth.ToBlockID(block) @@ -203,7 +203,7 @@ if err := l.state.AddL2Block(block); err != nil { return nil, fmt.Errorf("adding L2 block to state: %w", err) }   - l.Log.Info("added L2 block to local state", "block", eth.ToBlockID(block), "tx_count", len(block.Transactions()), "time", block.Time()) + l.Log.Info("Added L2 block to local state", "block", eth.ToBlockID(block), "tx_count", len(block.Transactions()), "time", block.Time()) return block, nil }   @@ -233,7 +233,7 @@ if l.lastStoredBlock == (eth.BlockID{}) { l.Log.Info("Starting batch-submitter work at safe-head", "safe", syncStatus.SafeL2) l.lastStoredBlock = syncStatus.SafeL2.ID() } else if l.lastStoredBlock.Number < syncStatus.SafeL2.Number { - l.Log.Warn("last submitted block lagged behind L2 safe head: batch submission will continue from the safe head now", "last", l.lastStoredBlock, "safe", syncStatus.SafeL2) + l.Log.Warn("Last submitted block lagged behind L2 safe head: batch submission will continue from the safe head now", "last", l.lastStoredBlock, "safe", syncStatus.SafeL2) l.lastStoredBlock = syncStatus.SafeL2.ID() }   @@ -276,10 +276,10 @@ go func() { for { select { case r := <-receiptsCh: - l.Log.Info("handling receipt", "id", r.ID) + l.Log.Info("Handling receipt", "id", r.ID) l.handleReceipt(r) case <-receiptLoopDone: - l.Log.Info("receipt processing loop done") + l.Log.Info("Receipt processing loop done") return } } @@ -382,7 +382,7 @@ } err := l.publishTxToL1(l.killCtx, queue, receiptsCh) if err != nil { if err != io.EOF { - l.Log.Error("error publishing tx to l1", "err", err) + l.Log.Error("Error publishing tx to l1", "err", err) } return } @@ -442,10 +442,10 @@ // Collect next transaction data txdata, err := l.state.TxData(l1tip.ID())   if err == io.EOF { - l.Log.Trace("no transaction data available") + l.Log.Trace("No transaction data available") return err } else if err != nil { - l.Log.Error("unable to get tx data", "err", err) + l.Log.Error("Unable to get tx data", "err", err) return err }   @@ -497,7 +497,7 @@ } } else { // sanity check if nf := len(txdata.frames); nf != 1 { - l.Log.Crit("unexpected number of frames in calldata tx", "num_frames", nf) + l.Log.Crit("Unexpected number of frames in calldata tx", "num_frames", nf) } data := txdata.CallData() // if plasma DA is enabled we post the txdata to the DA Provider and replace it with the commitment. @@ -509,13 +509,14 @@ // requeue frame if we fail to post to the DA Provider so it can be retried l.recordFailedTx(txdata.ID(), err) return nil } + l.Log.Info("Set plasma input", "commitment", comm, "tx", txdata.ID()) // signal plasma commitment tx with TxDataVersion1 data = comm.TxData() } candidate = l.calldataTxCandidate(data) }   - intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false) + intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false, nil) if err != nil { // we log instead of return an error here because txmgr can do its own gas estimation l.Log.Error("Failed to calculate intrinsic gas", "err", err) @@ -534,7 +535,7 @@ return nil, fmt.Errorf("generating blobs for tx data: %w", err) } size := data.Len() lastSize := len(data.frames[len(data.frames)-1].data) - l.Log.Info("building Blob transaction candidate", + l.Log.Info("Building Blob transaction candidate", "size", size, "last_size", lastSize, "num_blobs", len(blobs)) l.Metr.RecordBlobUsedBytes(lastSize) return &txmgr.TxCandidate{ @@ -544,7 +545,7 @@ }, nil }   func (l *BatchSubmitter) calldataTxCandidate(data []byte) *txmgr.TxCandidate { - l.Log.Info("building Calldata transaction candidate", "size", len(data)) + l.Log.Info("Building Calldata transaction candidate", "size", len(data)) return &txmgr.TxCandidate{ To: &l.RollupConfig.BatchInboxAddress, TxData: data,
diff --git OP/op-batcher/batcher/service.go CELO/op-batcher/batcher/service.go index 8ef40b346a0be3de0d5bbfcebd87038db319c032..1c35c1c4ea25f5dec961a8352f693ed619615084 100644 --- OP/op-batcher/batcher/service.go +++ CELO/op-batcher/batcher/service.go @@ -248,7 +248,7 @@ "max_channel_duration", cc.MaxChannelDuration, "channel_timeout", cc.ChannelTimeout, "sub_safety_margin", cc.SubSafetyMargin) if bs.UsePlasma { - bs.Log.Warn("Plasma Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues.") + bs.Log.Warn("Alt-DA Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues.") } bs.ChannelConfig = cc return nil @@ -282,19 +282,19 @@ }   func (bs *BatcherService) initMetricsServer(cfg *CLIConfig) error { if !cfg.MetricsConfig.Enabled { - bs.Log.Info("metrics disabled") + bs.Log.Info("Metrics disabled") return nil } m, ok := bs.Metrics.(opmetrics.RegistryMetricer) if !ok { return fmt.Errorf("metrics were enabled, but metricer %T does not expose registry for metrics-server", bs.Metrics) } - bs.Log.Debug("starting metrics server", "addr", cfg.MetricsConfig.ListenAddr, "port", cfg.MetricsConfig.ListenPort) + bs.Log.Debug("Starting metrics server", "addr", cfg.MetricsConfig.ListenAddr, "port", cfg.MetricsConfig.ListenPort) metricsSrv, err := opmetrics.StartServer(m.Registry(), cfg.MetricsConfig.ListenAddr, cfg.MetricsConfig.ListenPort) if err != nil { return fmt.Errorf("failed to start metrics server: %w", err) } - bs.Log.Info("started metrics server", "addr", metricsSrv.Addr()) + bs.Log.Info("Started metrics server", "addr", metricsSrv.Addr()) bs.metricsSrv = metricsSrv return nil }
diff --git OP/op-batcher/batcher/tx_data.go CELO/op-batcher/batcher/tx_data.go index 73e1adbbe179f7732241d23cfcaf953aa3379078..5937acf6f7974a920314c8678d956c5240dbc13b 100644 --- OP/op-batcher/batcher/tx_data.go +++ CELO/op-batcher/batcher/tx_data.go @@ -62,7 +62,7 @@ } return l }   -// Frame returns the single frame of this tx data. +// Frames returns the single frame of this tx data. func (td *txData) Frames() []frameData { return td.frames }
diff --git OP/op-batcher/flags/flags_test.go CELO/op-batcher/flags/flags_test.go index af303724e25d4b2a1b36bfe40f0d40ee021316a2..486342375b1f89f0ffebd123ef215a7ae0f89a9f 100644 --- OP/op-batcher/flags/flags_test.go +++ CELO/op-batcher/flags/flags_test.go @@ -5,6 +5,7 @@ "slices" "strings" "testing"   + plasma "github.com/ethereum-optimism/optimism/op-plasma" opservice "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-service/txmgr"   @@ -57,6 +58,14 @@ } }   func TestHasEnvVar(t *testing.T) { + // known exceptions to the number of env vars + expEnvVars := map[string]int{ + plasma.EnabledFlagName: 2, + plasma.DaServerAddressFlagName: 2, + plasma.VerifyOnReadFlagName: 2, + plasma.DaServiceFlag: 2, + } + for _, flag := range Flags { flag := flag flagName := flag.Names()[0] @@ -65,9 +74,13 @@ t.Run(flagName, func(t *testing.T) { envFlagGetter, ok := flag.(interface { GetEnvVars() []string }) - envFlags := envFlagGetter.GetEnvVars() require.True(t, ok, "must be able to cast the flag to an EnvVar interface") - require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") + envFlags := envFlagGetter.GetEnvVars() + if numEnvVars, ok := expEnvVars[flagName]; ok { + require.Equalf(t, numEnvVars, len(envFlags), "flags should have %d env vars", numEnvVars) + } else { + require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") + } }) } } @@ -92,7 +105,6 @@ GetEnvVars() []string }) envFlags := envFlagGetter.GetEnvVars() require.True(t, ok, "must be able to cast the flag to an EnvVar interface") - require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") expectedEnvVar := opservice.FlagNameToEnvVarName(flagName, "OP_BATCHER") require.Equal(t, expectedEnvVar, envFlags[0]) })
diff --git OP/op-chain-ops/cmd/celo-migrate/testdata/deploy-config-holesky-alfajores.json CELO/op-chain-ops/cmd/celo-migrate/testdata/deploy-config-holesky-alfajores.json new file mode 100644 index 0000000000000000000000000000000000000000..6b9dbe97e0682fb1129d33992733460bafb86c21 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/testdata/deploy-config-holesky-alfajores.json @@ -0,0 +1,89 @@ +{ + "l1StartingBlockTag": "0xbbed3612407993e67f8ca7a423b181837ae164a531941e78f5ee48e766d39cad", + + "l1ChainID": 17000, + "l2ChainID": 44787, + "l2BlockTime": 2, + "l1BlockTime": 12, + + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + + "p2pSequencerAddress": "0x644C82d76A43Fe9c76eda0EEd0f0DC17235c3005", + "batchInboxAddress": "0xff00000000000000000000000000000000044787", + "batchSenderAddress": "0x1660B1F70De0f32490b50f976e8983213dCF7FD5", + + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": 1718312256, + + "l2OutputOracleProposer": "0x1BA11Ec6581FC8C3e35D6E345aEC977796Ffe89b", + "l2OutputOracleChallenger": "0xc07C5A1fBF6c7BC6b4f321E7dd031c0E1E98d32d", + + "finalizationPeriodSeconds": 12, + + "proxyAdminOwner": "0xc07C5A1fBF6c7BC6b4f321E7dd031c0E1E98d32d", + "baseFeeVaultRecipient": "0xc07C5A1fBF6c7BC6b4f321E7dd031c0E1E98d32d", + "l1FeeVaultRecipient": "0xc07C5A1fBF6c7BC6b4f321E7dd031c0E1E98d32d", + "sequencerFeeVaultRecipient": "0xc07C5A1fBF6c7BC6b4f321E7dd031c0E1E98d32d", + "finalSystemOwner": "0xc07C5A1fBF6c7BC6b4f321E7dd031c0E1E98d32d", + "superchainConfigGuardian": "0xc07C5A1fBF6c7BC6b4f321E7dd031c0E1E98d32d", + + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + + "gasPriceOracleOverhead": 0, + "gasPriceOracleScalar": 1000000, + + "enableGovernance": false, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "0xc07C5A1fBF6c7BC6b4f321E7dd031c0E1E98d32d", + + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "l2GenesisRegolithTimeOffset": "0x0", + + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250, + "eip1559Elasticity": 6, + + "l2GenesisEcotoneTimeOffset": "0x0", + "l2GenesisDeltaTimeOffset": "0x0", + "l2GenesisCanyonTimeOffset": "0x0", + + "systemConfigStartBlock": 0, + + "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + + "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", + "faultGameMaxDepth": 44, + "faultGameClockExtension": 0, + "faultGameMaxClockDuration": 600, + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "faultGameSplitDepth": 14, + "faultGameWithdrawalDelay": 604800, + + "preimageOracleMinProposalSize": 1800000, + "preimageOracleChallengePeriod": 86400, + + "fundDevAccounts": false, + "useFaultProofs": false, + "proofMaturityDelaySeconds": 604800, + "disputeGameFinalityDelaySeconds": 302400, + "respectedGameType": 0, + + "usePlasma": false, + "daCommitmentType": "KeccakCommitment", + "daChallengeWindow": 160, + "daResolveWindow": 160, + "daBondSize": 1000000, + "daResolverRefundPercentage": 0 +}
diff --git OP/op-chain-ops/cmd/celo-migrate/testdata/deployment-l1-holesky.json CELO/op-chain-ops/cmd/celo-migrate/testdata/deployment-l1-holesky.json new file mode 100644 index 0000000000000000000000000000000000000000..b37b79f4d4c8f1ee70c7aae7e7f351713226b8a3 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/testdata/deployment-l1-holesky.json @@ -0,0 +1,34 @@ +{ + "AddressManager": "0x2d256f3b82f673Ee377C393fBF2Cf3DcA5D1D901", + "AnchorStateRegistry": "0x036fDE501893043825356Ce49dfd554809F07597", + "AnchorStateRegistryProxy": "0xe5077701c64782954d27384da76D95ABf320460f", + "DelayedWETH": "0x408Ad04Dd953958B080226025E17d6Ba12987EB7", + "DelayedWETHProxy": "0x27f7Ade64F031A39553Be8104bF8B0b410735845", + "DisputeGameFactory": "0xd7771F9687804Bba1D360B08AD9e4d8CB4523738", + "DisputeGameFactoryProxy": "0x193FdDF22D31c227f1Af1286cf2B051d701FF86E", + "L1CrossDomainMessenger": "0x1e3513a619AA4f2550CDD95709B92C1FE0397184", + "L1CrossDomainMessengerProxy": "0x35841aC1f5FdC5b812562adB17F6A0B9A178F643", + "L1ERC721Bridge": "0x695b01393f0539ec64AC316d4998E4130309efB0", + "L1ERC721BridgeProxy": "0x2b9C1e5b9a0D01256388cc4A0F8F290E839F2d82", + "L1StandardBridge": "0x2d1A818544b657Bc5d1E8c8B80F953bd0CA1C9B2", + "L1StandardBridgeProxy": "0xD10A531CB9b80BD507501F34D87Ad4083E9b7F98", + "L2OutputOracle": "0x04CD14625ff0Da62d6E0820a816b4dD3eCd0FF27", + "L2OutputOracleProxy": "0x5636f9D582DB69EAf1Eb9f05B0738225C91fBC1E", + "Mips": "0x60E1b8b535626Fc9fFCdf6147B45879634645771", + "OptimismMintableERC20Factory": "0x3fcd69a03857aA6e79AE9408fc7c887EE70FC145", + "OptimismMintableERC20FactoryProxy": "0x23c80F2503b93a58EC620D20b6b9B6AB8cCa2a12", + "OptimismPortal": "0xdF803FAC1d84a31Ff5aee841f11659f9a3787CE5", + "OptimismPortal2": "0x60bc423dDf0B24fa5104EcacAC5000674Ac3EBfB", + "OptimismPortalProxy": "0xa292B051eA58e2558243f4A9f74262B1796c9648", + "PreimageOracle": "0xEC19353B7364Fb85b9b0A57EaEEC6aCeBbFb6a53", + "ProtocolVersions": "0x077d61D4fb3378025950Bb60AD69179B38921107", + "ProtocolVersionsProxy": "0x791D5101840A547F1EE91148d34E061412A57ECD", + "ProxyAdmin": "0x4ddC758DA1697Ad58D86D03150872c042390dCa2", + "SafeProxyFactory": "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", + "SafeSingleton": "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552", + "SuperchainConfig": "0xA4f7dB67A6e098613B107be3F8441475Ec30FCC2", + "SuperchainConfigProxy": "0xB21214DA32a85A0d43372310D62095cf91d67765", + "SystemConfig": "0xeFA98Ba3ada6c6AC4bB84074820685E1F01C835d", + "SystemConfigProxy": "0x733043Aa78d25F6759d9e6Ce2B2897bE6d630E08", + "SystemOwnerSafe": "0xD2a6B91aB77691D6F8688eAFA7a5f188bc5baA3a" +}
diff --git OP/op-chain-ops/cmd/celo-migrate/testdata/rollup-config.json CELO/op-chain-ops/cmd/celo-migrate/testdata/rollup-config.json new file mode 100644 index 0000000000000000000000000000000000000000..8dfd1f25e28d86be0d6188ab982c2439b077a2f0 --- /dev/null +++ CELO/op-chain-ops/cmd/celo-migrate/testdata/rollup-config.json @@ -0,0 +1,36 @@ +{ + "genesis": { + "l1": { + "hash": "0xbbed3612407993e67f8ca7a423b181837ae164a531941e78f5ee48e766d39cad", + "number": 1729797 + }, + "l2": { + "hash": "0x2664d0a1f45dc9a010e553e815a25f33c6d949cbb0d38e179c6209fc0486aa41", + "number": 23912613 + }, + "l2_time": 1718312256, + "system_config": { + "batcherAddr": "0x1660b1f70de0f32490b50f976e8983213dcf7fd5", + "overhead": "0x0000000000000000000000000000000000000000000000000000000000000000", + "scalar": "0x00000000000000000000000000000000000000000000000000000000000f4240", + "gasLimit": 30000000 + } + }, + "block_time": 2, + "max_sequencer_drift": 600, + "seq_window_size": 3600, + "channel_timeout": 300, + "l1_chain_id": 17000, + "l2_chain_id": 44787, + "regolith_time": 0, + "cel2_time": 0, + "canyon_time": 0, + "delta_time": 0, + "ecotone_time": 0, + "batch_inbox_address": "0xff00000000000000000000000000000000044787", + "deposit_contract_address": "0xa292b051ea58e2558243f4a9f74262b1796c9648", + "l1_system_config_address": "0x733043aa78d25f6759d9e6ce2b2897be6d630e08", + "protocol_versions_address": "0x0000000000000000000000000000000000000000", + "da_challenge_contract_address": "0x0000000000000000000000000000000000000000" +} +
diff --git OP/op-challenger/game/fault/contracts/oracle.go CELO/op-challenger/game/fault/contracts/oracle.go index be2520bef8b5b139b8151cb33a30922f0e38325d..bc0ff338615c94df26b2a37da86f23bd3b2ef8a2 100644 --- OP/op-challenger/game/fault/contracts/oracle.go +++ CELO/op-challenger/game/fault/contracts/oracle.go @@ -129,8 +129,17 @@ } }   func (c *PreimageOracleContract) InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error) { + bond, err := c.GetMinBondLPP(context.Background()) + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to get min bond for large preimage proposal: %w", err) + } call := c.contract.Call(methodInitLPP, uuid, partOffset, claimedSize) - return call.ToTxCandidate() + candidate, err := call.ToTxCandidate() + if err != nil { + return txmgr.TxCandidate{}, fmt.Errorf("failed to create initLPP tx candidate: %w", err) + } + candidate.Value = bond + return candidate, nil }   func (c *PreimageOracleContract) AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error) {
diff --git OP/op-challenger/game/fault/contracts/oracle_test.go CELO/op-challenger/game/fault/contracts/oracle_test.go index 480d738bd478b674bdd48449766613969b5daea7..7bb8158ef6a50b65efde7bb76f90ae069a4305fc 100644 --- OP/op-challenger/game/fault/contracts/oracle_test.go +++ CELO/op-challenger/game/fault/contracts/oracle_test.go @@ -164,6 +164,8 @@ uuid := big.NewInt(123) partOffset := uint32(1) claimedSize := uint32(2) + bond := big.NewInt(42984) + stubRpc.SetResponse(oracleAddr, methodMinBondSizeLPP, rpcblock.Latest, nil, []interface{}{bond}) stubRpc.SetResponse(oracleAddr, methodInitLPP, rpcblock.Latest, []interface{}{ uuid, partOffset, @@ -173,6 +175,7 @@ tx, err := oracle.InitLargePreimage(uuid, partOffset, claimedSize) require.NoError(t, err) stubRpc.VerifyTxCandidate(tx) + require.Truef(t, bond.Cmp(tx.Value) == 0, "Expected bond %v got %v", bond, tx.Value) }   func TestPreimageOracleContract_AddLeaves(t *testing.T) {
diff --git OP/op-challenger/game/fault/preimages/large.go CELO/op-challenger/game/fault/preimages/large.go index da28fdcf16fdbd9d692389aa8c3c49dab9f09c21..fe311bdfb1504ba3b465ccc2c5708478f424922f 100644 --- OP/op-challenger/game/fault/preimages/large.go +++ CELO/op-challenger/game/fault/preimages/large.go @@ -164,11 +164,6 @@ candidate, err := p.contract.InitLargePreimage(uuid, partOffset, claimedSize) if err != nil { return fmt.Errorf("failed to create pre-image oracle tx: %w", err) } - bond, err := p.contract.GetMinBondLPP(context.Background()) - if err != nil { - return fmt.Errorf("failed to get min bond for large preimage proposal: %w", err) - } - candidate.Value = bond if err := p.txSender.SendAndWaitSimple("init large preimage", candidate); err != nil { return fmt.Errorf("failed to populate pre-image oracle: %w", err) }
diff --git OP/op-challenger/game/fault/trace/asterisc/executor.go CELO/op-challenger/game/fault/trace/asterisc/executor.go deleted file mode 100644 index b84d5e444568a1b3449ec7831d4b1674890301ca..0000000000000000000000000000000000000000 --- OP/op-challenger/game/fault/trace/asterisc/executor.go +++ /dev/null @@ -1,127 +0,0 @@ -package asterisc - -import ( - "context" - "fmt" - "math" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/ethereum-optimism/optimism/op-challenger/config" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" - "github.com/ethereum/go-ethereum/log" -) - -type Executor struct { - logger log.Logger - metrics AsteriscMetricer - l1 string - l1Beacon string - l2 string - inputs utils.LocalGameInputs - asterisc string - server string - network string - rollupConfig string - l2Genesis string - absolutePreState string - snapshotFreq uint - infoFreq uint - selectSnapshot utils.SnapshotSelect - cmdExecutor utils.CmdExecutor -} - -func NewExecutor(logger log.Logger, m AsteriscMetricer, cfg *config.Config, prestate string, inputs utils.LocalGameInputs) *Executor { - return &Executor{ - logger: logger, - metrics: m, - l1: cfg.L1EthRpc, - l1Beacon: cfg.L1Beacon, - l2: cfg.L2Rpc, - inputs: inputs, - asterisc: cfg.AsteriscBin, - server: cfg.AsteriscServer, - network: cfg.AsteriscNetwork, - rollupConfig: cfg.AsteriscRollupConfigPath, - l2Genesis: cfg.AsteriscL2GenesisPath, - absolutePreState: prestate, - snapshotFreq: cfg.AsteriscSnapshotFreq, - infoFreq: cfg.AsteriscInfoFreq, - selectSnapshot: utils.FindStartingSnapshot, - cmdExecutor: utils.RunCmd, - } -} - -// GenerateProof executes asterisc to generate a proof at the specified trace index. -// The proof is stored at the specified directory. -func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) error { - return e.generateProof(ctx, dir, i, i) -} - -// generateProof executes asterisc from the specified starting trace index until the end trace index. -// The proof is stored at the specified directory. -func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64, end uint64, extraAsteriscArgs ...string) error { - snapshotDir := filepath.Join(dir, utils.SnapsDir) - start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin) - if err != nil { - return fmt.Errorf("find starting snapshot: %w", err) - } - proofDir := filepath.Join(dir, proofsDir) - dataDir := utils.PreimageDir(dir) - lastGeneratedState := filepath.Join(dir, utils.FinalState) - args := []string{ - "run", - "--input", start, - "--output", lastGeneratedState, - "--meta", "", - "--info-at", "%" + strconv.FormatUint(uint64(e.infoFreq), 10), - "--proof-at", "=" + strconv.FormatUint(end, 10), - "--proof-fmt", filepath.Join(proofDir, "%d.json.gz"), - "--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10), - "--snapshot-fmt", filepath.Join(snapshotDir, "%d.json.gz"), - } - if end < math.MaxUint64 { - args = append(args, "--stop-at", "="+strconv.FormatUint(end+1, 10)) - } - args = append(args, extraAsteriscArgs...) - args = append(args, - "--", - e.server, "--server", - "--l1", e.l1, - "--l1.beacon", e.l1Beacon, - "--l2", e.l2, - "--datadir", dataDir, - "--l1.head", e.inputs.L1Head.Hex(), - "--l2.head", e.inputs.L2Head.Hex(), - "--l2.outputroot", e.inputs.L2OutputRoot.Hex(), - "--l2.claim", e.inputs.L2Claim.Hex(), - "--l2.blocknumber", e.inputs.L2BlockNumber.Text(10), - ) - if e.network != "" { - args = append(args, "--network", e.network) - } - if e.rollupConfig != "" { - args = append(args, "--rollup.config", e.rollupConfig) - } - if e.l2Genesis != "" { - args = append(args, "--l2.genesis", e.l2Genesis) - } - - if err := os.MkdirAll(snapshotDir, 0755); err != nil { - return fmt.Errorf("could not create snapshot directory %v: %w", snapshotDir, err) - } - if err := os.MkdirAll(dataDir, 0755); err != nil { - return fmt.Errorf("could not create preimage cache directory %v: %w", dataDir, err) - } - if err := os.MkdirAll(proofDir, 0755); err != nil { - return fmt.Errorf("could not create proofs directory %v: %w", proofDir, err) - } - e.logger.Info("Generating trace", "proof", end, "cmd", e.asterisc, "args", strings.Join(args, ", ")) - execStart := time.Now() - err = e.cmdExecutor(ctx, e.logger.New("proof", end), e.asterisc, args...) - e.metrics.RecordAsteriscExecutionTime(time.Since(execStart).Seconds()) - return err -}
diff --git OP/op-challenger/game/fault/trace/asterisc/provider.go CELO/op-challenger/game/fault/trace/asterisc/provider.go index cbf7b6241ae2d6f363eb7e5f246772da74ff57ea..5c481777b6ce1e15710f8313ebd4ea7ce1f2f588 100644 --- OP/op-challenger/game/fault/trace/asterisc/provider.go +++ CELO/op-challenger/game/fault/trace/asterisc/provider.go @@ -12,6 +12,7 @@ "path/filepath"   "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-program/host/kvstore" "github.com/ethereum-optimism/optimism/op-service/ioutil" @@ -19,15 +20,6 @@ "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" )   -const ( - proofsDir = "proofs" - diskStateCache = "state.json.gz" -) - -type AsteriscMetricer interface { - RecordAsteriscExecutionTime(t float64) -} - type AsteriscTraceProvider struct { logger log.Logger dir string @@ -43,14 +35,14 @@ // Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace. lastStep uint64 }   -func NewTraceProvider(logger log.Logger, m AsteriscMetricer, cfg *config.Config, prestateProvider types.PrestateProvider, asteriscPrestate string, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *AsteriscTraceProvider { +func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, prestateProvider types.PrestateProvider, asteriscPrestate string, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *AsteriscTraceProvider { return &AsteriscTraceProvider{ logger: logger, dir: dir, prestate: asteriscPrestate, - generator: NewExecutor(logger, m, cfg, asteriscPrestate, localInputs), + generator: vm.NewExecutor(logger, m, cfg, asteriscPrestate, localInputs), gameDepth: gameDepth, - preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get), + preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(vm.PreimageDir(dir)).Get), PrestateProvider: prestateProvider, } } @@ -116,7 +108,7 @@ // If the last step is tracked, set i to the last step to generate or load the final proof if p.lastStep != 0 && i > p.lastStep { i = p.lastStep } - path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json.gz", i)) + path := filepath.Join(p.dir, utils.ProofsDir, fmt.Sprintf("%d.json.gz", i)) file, err := ioutil.OpenDecompressed(path) if errors.Is(err, os.ErrNotExist) { if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil { @@ -167,7 +159,7 @@ return &proof, nil }   func (c *AsteriscTraceProvider) finalState() (*VMState, error) { - state, err := parseState(filepath.Join(c.dir, utils.FinalState)) + state, err := parseState(filepath.Join(c.dir, vm.FinalState)) if err != nil { return nil, fmt.Errorf("cannot read final state: %w", err) } @@ -180,21 +172,21 @@ type AsteriscTraceProviderForTest struct { *AsteriscTraceProvider }   -func NewTraceProviderForTest(logger log.Logger, m AsteriscMetricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *AsteriscTraceProviderForTest { +func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *AsteriscTraceProviderForTest { p := &AsteriscTraceProvider{ logger: logger, dir: dir, prestate: cfg.AsteriscAbsolutePreState, - generator: NewExecutor(logger, m, cfg, cfg.AsteriscAbsolutePreState, localInputs), + generator: vm.NewExecutor(logger, m, cfg.Asterisc, cfg.AsteriscAbsolutePreState, localInputs), gameDepth: gameDepth, - preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get), + preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(vm.PreimageDir(dir)).Get), } return &AsteriscTraceProviderForTest{p} }   func (p *AsteriscTraceProviderForTest) FindStep(ctx context.Context, start uint64, preimage utils.PreimageOpt) (uint64, error) { // Run asterisc to find the step that meets the preimage conditions - if err := p.generator.(*Executor).generateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil { + if err := p.generator.(*vm.Executor).DoGenerateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil { return 0, fmt.Errorf("generate asterisc trace (until preimage read): %w", err) } // Load the step from the state asterisc finished with
diff --git OP/op-challenger/game/fault/trace/asterisc/provider_test.go CELO/op-challenger/game/fault/trace/asterisc/provider_test.go index 950a5ad70375a6acbbf0aa7dcf6ac785d74358d6..939a27decc304e256a04b8b7118b3cd1e9dc4844 100644 --- OP/op-challenger/game/fault/trace/asterisc/provider_test.go +++ CELO/op-challenger/game/fault/trace/asterisc/provider_test.go @@ -12,6 +12,7 @@ "path/filepath" "testing"   "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/testlog" @@ -205,12 +206,12 @@ srcDir := filepath.Join("test_data", "proofs") entries, err := testData.ReadDir(srcDir) require.NoError(t, err) dataDir := t.TempDir() - require.NoError(t, os.Mkdir(filepath.Join(dataDir, proofsDir), 0o777)) + require.NoError(t, os.Mkdir(filepath.Join(dataDir, utils.ProofsDir), 0o777)) for _, entry := range entries { path := filepath.Join(srcDir, entry.Name()) file, err := testData.ReadFile(path) require.NoErrorf(t, err, "reading %v", path) - proofFile := filepath.Join(dataDir, proofsDir, entry.Name()+".gz") + proofFile := filepath.Join(dataDir, utils.ProofsDir, entry.Name()+".gz") err = ioutil.WriteCompressedBytes(proofFile, file, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644) require.NoErrorf(t, err, "writing %v", path) } @@ -241,7 +242,7 @@ var data []byte var err error if e.finalState != nil && e.finalState.Step <= i { // Requesting a trace index past the end of the trace - proofFile = filepath.Join(dir, utils.FinalState) + proofFile = filepath.Join(dir, vm.FinalState) data, err = json.Marshal(e.finalState) if err != nil { return err @@ -249,7 +250,7 @@ } return ioutil.WriteCompressedBytes(proofFile, data, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644) } if e.proof != nil { - proofFile = filepath.Join(dir, proofsDir, fmt.Sprintf("%d.json.gz", i)) + proofFile = filepath.Join(dir, utils.ProofsDir, fmt.Sprintf("%d.json.gz", i)) data, err = json.Marshal(e.proof) if err != nil { return err
diff --git OP/op-challenger/game/fault/trace/asterisc/state.go CELO/op-challenger/game/fault/trace/asterisc/state.go index b766cda6f50caa4b37307e2185bbbf16c66b1726..ac269d192924b053edccf6532b400c0e7790e9dd 100644 --- OP/op-challenger/game/fault/trace/asterisc/state.go +++ CELO/op-challenger/game/fault/trace/asterisc/state.go @@ -7,6 +7,7 @@ "io"   "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum/go-ethereum/common" )   var asteriscWitnessLen = 362 @@ -14,11 +15,11 @@ // The state struct will be read from json. // other fields included in json are specific to FPVM implementation, and not required for trace provider. type VMState struct { - PC uint64 `json:"pc"` - Exited bool `json:"exited"` - Step uint64 `json:"step"` - Witness []byte `json:"witness"` - StateHash [32]byte `json:"stateHash"` + PC uint64 `json:"pc"` + Exited bool `json:"exited"` + Step uint64 `json:"step"` + Witness []byte `json:"witness"` + StateHash common.Hash `json:"stateHash"` }   func (state *VMState) validateStateHash() error {
diff --git OP/op-challenger/game/fault/trace/asterisc/test_data/state.json CELO/op-challenger/game/fault/trace/asterisc/test_data/state.json index a1bf2e5b412e4b2d14002e7f9ae052047a283b20..00dfc2d666c84b89561d6eea4f4fdc67e1159faa 100644 --- OP/op-challenger/game/fault/trace/asterisc/test_data/state.json +++ CELO/op-challenger/game/fault/trace/asterisc/test_data/state.json @@ -3,38 +3,5 @@ "pc": 0, "exited": false, "step": 0, "witness": "wOSi8Cm62dDmKt1OGwxlLrSznk6zE4ghp7evP1rfrXYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIGCAAAAAAAAAAAAAAAAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "stateHash": [ - 3, - 33, - 111, - 220, - 74, - 123, - 253, - 76, - 113, - 96, - 250, - 148, - 109, - 27, - 254, - 69, - 29, - 19, - 255, - 50, - 218, - 73, - 102, - 9, - 254, - 24, - 53, - 82, - 130, - 185, - 16, - 198 - ] + "stateHash": "0x03216fdc4a7bfd4c7160fa946d1bfe451d13ff32da496609fe18355282b910c6" }
diff --git OP/op-challenger/game/fault/trace/cannon/executor.go CELO/op-challenger/game/fault/trace/cannon/executor.go deleted file mode 100644 index 688fd87d7fa2c7daf0d5ed8619aec2ad04f4d025..0000000000000000000000000000000000000000 --- OP/op-challenger/game/fault/trace/cannon/executor.go +++ /dev/null @@ -1,127 +0,0 @@ -package cannon - -import ( - "context" - "fmt" - "math" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/ethereum-optimism/optimism/op-challenger/config" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" - "github.com/ethereum/go-ethereum/log" -) - -type Executor struct { - logger log.Logger - metrics CannonMetricer - l1 string - l1Beacon string - l2 string - inputs utils.LocalGameInputs - cannon string - server string - network string - rollupConfig string - l2Genesis string - absolutePreState string - snapshotFreq uint - infoFreq uint - selectSnapshot utils.SnapshotSelect - cmdExecutor utils.CmdExecutor -} - -func NewExecutor(logger log.Logger, m CannonMetricer, cfg *config.Config, prestate string, inputs utils.LocalGameInputs) *Executor { - return &Executor{ - logger: logger, - metrics: m, - l1: cfg.L1EthRpc, - l1Beacon: cfg.L1Beacon, - l2: cfg.L2Rpc, - inputs: inputs, - cannon: cfg.CannonBin, - server: cfg.CannonServer, - network: cfg.CannonNetwork, - rollupConfig: cfg.CannonRollupConfigPath, - l2Genesis: cfg.CannonL2GenesisPath, - absolutePreState: prestate, - snapshotFreq: cfg.CannonSnapshotFreq, - infoFreq: cfg.CannonInfoFreq, - selectSnapshot: utils.FindStartingSnapshot, - cmdExecutor: utils.RunCmd, - } -} - -// GenerateProof executes cannon to generate a proof at the specified trace index. -// The proof is stored at the specified directory. -func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) error { - return e.generateProof(ctx, dir, i, i) -} - -// generateProof executes cannon from the specified starting trace index until the end trace index. -// The proof is stored at the specified directory. -func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64, end uint64, extraCannonArgs ...string) error { - snapshotDir := filepath.Join(dir, utils.SnapsDir) - start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin) - if err != nil { - return fmt.Errorf("find starting snapshot: %w", err) - } - proofDir := filepath.Join(dir, utils.ProofsDir) - dataDir := utils.PreimageDir(dir) - lastGeneratedState := filepath.Join(dir, utils.FinalState) - args := []string{ - "run", - "--input", start, - "--output", lastGeneratedState, - "--meta", "", - "--info-at", "%" + strconv.FormatUint(uint64(e.infoFreq), 10), - "--proof-at", "=" + strconv.FormatUint(end, 10), - "--proof-fmt", filepath.Join(proofDir, "%d.json.gz"), - "--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10), - "--snapshot-fmt", filepath.Join(snapshotDir, "%d.json.gz"), - } - if end < math.MaxUint64 { - args = append(args, "--stop-at", "="+strconv.FormatUint(end+1, 10)) - } - args = append(args, extraCannonArgs...) - args = append(args, - "--", - e.server, "--server", - "--l1", e.l1, - "--l1.beacon", e.l1Beacon, - "--l2", e.l2, - "--datadir", dataDir, - "--l1.head", e.inputs.L1Head.Hex(), - "--l2.head", e.inputs.L2Head.Hex(), - "--l2.outputroot", e.inputs.L2OutputRoot.Hex(), - "--l2.claim", e.inputs.L2Claim.Hex(), - "--l2.blocknumber", e.inputs.L2BlockNumber.Text(10), - ) - if e.network != "" { - args = append(args, "--network", e.network) - } - if e.rollupConfig != "" { - args = append(args, "--rollup.config", e.rollupConfig) - } - if e.l2Genesis != "" { - args = append(args, "--l2.genesis", e.l2Genesis) - } - - if err := os.MkdirAll(snapshotDir, 0755); err != nil { - return fmt.Errorf("could not create snapshot directory %v: %w", snapshotDir, err) - } - if err := os.MkdirAll(dataDir, 0755); err != nil { - return fmt.Errorf("could not create preimage cache directory %v: %w", dataDir, err) - } - if err := os.MkdirAll(proofDir, 0755); err != nil { - return fmt.Errorf("could not create proofs directory %v: %w", proofDir, err) - } - e.logger.Info("Generating trace", "proof", end, "cmd", e.cannon, "args", strings.Join(args, ", ")) - execStart := time.Now() - err = e.cmdExecutor(ctx, e.logger.New("proof", end), e.cannon, args...) - e.metrics.RecordCannonExecutionTime(time.Since(execStart).Seconds()) - return err -}
diff --git OP/op-challenger/game/fault/trace/cannon/executor_test.go CELO/op-challenger/game/fault/trace/cannon/executor_test.go deleted file mode 100644 index 4f20c426e7780951c1797b76015c2e181a293040..0000000000000000000000000000000000000000 --- OP/op-challenger/game/fault/trace/cannon/executor_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package cannon - -import ( - "context" - "fmt" - "math" - "math/big" - "os" - "path/filepath" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/op-challenger/config" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" - "github.com/ethereum-optimism/optimism/op-challenger/metrics" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/require" -) - -const execTestCannonPrestate = "/foo/pre.json" - -func TestGenerateProof(t *testing.T) { - input := "starting.json" - tempDir := t.TempDir() - dir := filepath.Join(tempDir, "gameDir") - cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", "http://localhost:9096", "http://localhost:9095", tempDir, config.TraceTypeCannon) - cfg.L2Rpc = "http://localhost:9999" - prestate := "pre.json" - cfg.CannonBin = "./bin/cannon" - cfg.CannonServer = "./bin/op-program" - cfg.CannonSnapshotFreq = 500 - cfg.CannonInfoFreq = 900 - - inputs := utils.LocalGameInputs{ - L1Head: common.Hash{0x11}, - L2Head: common.Hash{0x22}, - L2OutputRoot: common.Hash{0x33}, - L2Claim: common.Hash{0x44}, - L2BlockNumber: big.NewInt(3333), - } - captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) { - m := &cannonDurationMetrics{} - executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, &cfg, prestate, inputs) - executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) { - return input, nil - } - var binary string - var subcommand string - args := make(map[string]string) - executor.cmdExecutor = func(ctx context.Context, l log.Logger, b string, a ...string) error { - binary = b - subcommand = a[0] - for i := 1; i < len(a); { - if a[i] == "--" { - // Skip over the divider between cannon and server program - i += 1 - continue - } - args[a[i]] = a[i+1] - i += 2 - } - return nil - } - err := executor.GenerateProof(context.Background(), dir, proofAt) - require.NoError(t, err) - require.Equal(t, 1, m.executionTimeRecordCount, "Should record cannon execution time") - return binary, subcommand, args - } - - t.Run("Network", func(t *testing.T) { - cfg.CannonNetwork = "mainnet" - cfg.CannonRollupConfigPath = "" - cfg.CannonL2GenesisPath = "" - binary, subcommand, args := captureExec(t, cfg, 150_000_000) - require.DirExists(t, filepath.Join(dir, utils.PreimagesDir)) - require.DirExists(t, filepath.Join(dir, utils.ProofsDir)) - require.DirExists(t, filepath.Join(dir, utils.SnapsDir)) - require.Equal(t, cfg.CannonBin, binary) - require.Equal(t, "run", subcommand) - require.Equal(t, input, args["--input"]) - require.Contains(t, args, "--meta") - require.Equal(t, "", args["--meta"]) - require.Equal(t, filepath.Join(dir, utils.FinalState), args["--output"]) - require.Equal(t, "=150000000", args["--proof-at"]) - require.Equal(t, "=150000001", args["--stop-at"]) - require.Equal(t, "%500", args["--snapshot-at"]) - require.Equal(t, "%900", args["--info-at"]) - // Slight quirk of how we pair off args - // The server binary winds up as the key and the first arg --server as the value which has no value - // Then everything else pairs off correctly again - require.Equal(t, "--server", args[cfg.CannonServer]) - require.Equal(t, cfg.L1EthRpc, args["--l1"]) - require.Equal(t, cfg.L1Beacon, args["--l1.beacon"]) - require.Equal(t, cfg.L2Rpc, args["--l2"]) - require.Equal(t, filepath.Join(dir, utils.PreimagesDir), args["--datadir"]) - require.Equal(t, filepath.Join(dir, utils.ProofsDir, "%d.json.gz"), args["--proof-fmt"]) - require.Equal(t, filepath.Join(dir, utils.SnapsDir, "%d.json.gz"), args["--snapshot-fmt"]) - require.Equal(t, cfg.CannonNetwork, args["--network"]) - require.NotContains(t, args, "--rollup.config") - require.NotContains(t, args, "--l2.genesis") - - // Local game inputs - require.Equal(t, inputs.L1Head.Hex(), args["--l1.head"]) - require.Equal(t, inputs.L2Head.Hex(), args["--l2.head"]) - require.Equal(t, inputs.L2OutputRoot.Hex(), args["--l2.outputroot"]) - require.Equal(t, inputs.L2Claim.Hex(), args["--l2.claim"]) - require.Equal(t, "3333", args["--l2.blocknumber"]) - }) - - t.Run("RollupAndGenesis", func(t *testing.T) { - cfg.CannonNetwork = "" - cfg.CannonRollupConfigPath = "rollup.json" - cfg.CannonL2GenesisPath = "genesis.json" - _, _, args := captureExec(t, cfg, 150_000_000) - require.NotContains(t, args, "--network") - require.Equal(t, cfg.CannonRollupConfigPath, args["--rollup.config"]) - require.Equal(t, cfg.CannonL2GenesisPath, args["--l2.genesis"]) - }) - - t.Run("NoStopAtWhenProofIsMaxUInt", func(t *testing.T) { - cfg.CannonNetwork = "mainnet" - cfg.CannonRollupConfigPath = "rollup.json" - cfg.CannonL2GenesisPath = "genesis.json" - _, _, args := captureExec(t, cfg, math.MaxUint64) - // stop-at would need to be one more than the proof step which would overflow back to 0 - // so expect that it will be omitted. We'll ultimately want cannon to execute until the program exits. - require.NotContains(t, args, "--stop-at") - }) -} - -func TestRunCmdLogsOutput(t *testing.T) { - bin := "/bin/echo" - if _, err := os.Stat(bin); err != nil { - t.Skip(bin, " not available", err) - } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - logger, logs := testlog.CaptureLogger(t, log.LevelInfo) - err := utils.RunCmd(ctx, logger, bin, "Hello World") - require.NoError(t, err) - levelFilter := testlog.NewLevelFilter(log.LevelInfo) - msgFilter := testlog.NewMessageFilter("Hello World") - require.NotNil(t, logs.FindLog(levelFilter, msgFilter)) -} - -func TestFindStartingSnapshot(t *testing.T) { - logger := testlog.Logger(t, log.LevelInfo) - - withSnapshots := func(t *testing.T, files ...string) string { - dir := t.TempDir() - for _, file := range files { - require.NoError(t, os.WriteFile(fmt.Sprintf("%v/%v", dir, file), nil, 0o644)) - } - return dir - } - - t.Run("UsePrestateWhenSnapshotsDirDoesNotExist", func(t *testing.T) { - dir := t.TempDir() - snapshot, err := utils.FindStartingSnapshot(logger, filepath.Join(dir, "doesNotExist"), execTestCannonPrestate, 1200) - require.NoError(t, err) - require.Equal(t, execTestCannonPrestate, snapshot) - }) - - t.Run("UsePrestateWhenSnapshotsDirEmpty", func(t *testing.T) { - dir := withSnapshots(t) - snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 1200) - require.NoError(t, err) - require.Equal(t, execTestCannonPrestate, snapshot) - }) - - t.Run("UsePrestateWhenNoSnapshotBeforeTraceIndex", func(t *testing.T) { - dir := withSnapshots(t, "100.json", "200.json") - snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 99) - require.NoError(t, err) - require.Equal(t, execTestCannonPrestate, snapshot) - - snapshot, err = utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 100) - require.NoError(t, err) - require.Equal(t, execTestCannonPrestate, snapshot) - }) - - t.Run("UseClosestAvailableSnapshot", func(t *testing.T) { - dir := withSnapshots(t, "100.json.gz", "123.json.gz", "250.json.gz") - - snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 101) - require.NoError(t, err) - require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot) - - snapshot, err = utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 123) - require.NoError(t, err) - require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot) - - snapshot, err = utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 124) - require.NoError(t, err) - require.Equal(t, filepath.Join(dir, "123.json.gz"), snapshot) - - snapshot, err = utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 256) - require.NoError(t, err) - require.Equal(t, filepath.Join(dir, "250.json.gz"), snapshot) - }) - - t.Run("IgnoreDirectories", func(t *testing.T) { - dir := withSnapshots(t, "100.json.gz") - require.NoError(t, os.Mkdir(filepath.Join(dir, "120.json.gz"), 0o777)) - snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 150) - require.NoError(t, err) - require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot) - }) - - t.Run("IgnoreUnexpectedFiles", func(t *testing.T) { - dir := withSnapshots(t, ".file", "100.json.gz", "foo", "bar.json.gz") - snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 150) - require.NoError(t, err) - require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot) - }) -} - -type cannonDurationMetrics struct { - metrics.NoopMetricsImpl - executionTimeRecordCount int -} - -func (c *cannonDurationMetrics) RecordCannonExecutionTime(_ float64) { - c.executionTimeRecordCount++ -}
diff --git OP/op-challenger/game/fault/trace/cannon/prestate_test.go CELO/op-challenger/game/fault/trace/cannon/prestate_test.go index 1297da54bd887103ee88ece84d693207d645a07e..a8616f1711808af738ff7ec8e401677dd4c1c556 100644 --- OP/op-challenger/game/fault/trace/cannon/prestate_test.go +++ CELO/op-challenger/game/fault/trace/cannon/prestate_test.go @@ -44,15 +44,17 @@ state := mipsevm.State{ Memory: mipsevm.NewMemory(), PreimageKey: common.HexToHash("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), PreimageOffset: 0, - PC: 0, - NextPC: 1, - LO: 0, - HI: 0, - Heap: 0, - ExitCode: 0, - Exited: false, - Step: 0, - Registers: [32]uint32{}, + Cpu: mipsevm.CpuScalars{ + PC: 0, + NextPC: 1, + LO: 0, + HI: 0, + }, + Heap: 0, + ExitCode: 0, + Exited: false, + Step: 0, + Registers: [32]uint32{}, } expected, err := state.EncodeWitness().StateHash() require.NoError(t, err)
diff --git OP/op-challenger/game/fault/trace/cannon/provider.go CELO/op-challenger/game/fault/trace/cannon/provider.go index 704d88e2e6a413ca1c8795d3eede39b9357d2ecb..c565b9b39b75df40161b8a7dc679a2292469ca97 100644 --- OP/op-challenger/game/fault/trace/cannon/provider.go +++ CELO/op-challenger/game/fault/trace/cannon/provider.go @@ -12,6 +12,7 @@ "path/filepath"   "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-program/host/kvstore" "github.com/ethereum-optimism/optimism/op-service/ioutil" @@ -21,10 +22,6 @@ "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/cannon/mipsevm" ) - -type CannonMetricer interface { - RecordCannonExecutionTime(t float64) -}   type CannonTraceProvider struct { logger log.Logger @@ -41,14 +38,14 @@ // Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace. lastStep uint64 }   -func NewTraceProvider(logger log.Logger, m CannonMetricer, cfg *config.Config, prestateProvider types.PrestateProvider, prestate string, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProvider { +func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, prestateProvider types.PrestateProvider, prestate string, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProvider { return &CannonTraceProvider{ logger: logger, dir: dir, prestate: prestate, - generator: NewExecutor(logger, m, cfg, prestate, localInputs), + generator: vm.NewExecutor(logger, m, cfg, prestate, localInputs), gameDepth: gameDepth, - preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get), + preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(vm.PreimageDir(dir)).Get), PrestateProvider: prestateProvider, } } @@ -170,7 +167,7 @@ return &proof, nil }   func (c *CannonTraceProvider) finalState() (*mipsevm.State, error) { - state, err := parseState(filepath.Join(c.dir, utils.FinalState)) + state, err := parseState(filepath.Join(c.dir, vm.FinalState)) if err != nil { return nil, fmt.Errorf("cannot read final state: %w", err) } @@ -183,21 +180,21 @@ type CannonTraceProviderForTest struct { *CannonTraceProvider }   -func NewTraceProviderForTest(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProviderForTest { +func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProviderForTest { p := &CannonTraceProvider{ logger: logger, dir: dir, prestate: cfg.CannonAbsolutePreState, - generator: NewExecutor(logger, m, cfg, cfg.CannonAbsolutePreState, localInputs), + generator: vm.NewExecutor(logger, m, cfg.Cannon, cfg.CannonAbsolutePreState, localInputs), gameDepth: gameDepth, - preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get), + preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(vm.PreimageDir(dir)).Get), } return &CannonTraceProviderForTest{p} }   func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64, preimage utils.PreimageOpt) (uint64, error) { // Run cannon to find the step that meets the preimage conditions - if err := p.generator.(*Executor).generateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil { + if err := p.generator.(*vm.Executor).DoGenerateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil { return 0, fmt.Errorf("generate cannon trace (until preimage read): %w", err) } // Load the step from the state cannon finished with
diff --git OP/op-challenger/game/fault/trace/cannon/provider_test.go CELO/op-challenger/game/fault/trace/cannon/provider_test.go index 388bd151991bc48bee6f9d2e11d09b91b060c3fb..94277ed88399b1ef6e90860f3b693ff47ffa657b 100644 --- OP/op-challenger/game/fault/trace/cannon/provider_test.go +++ CELO/op-challenger/game/fault/trace/cannon/provider_test.go @@ -13,6 +13,7 @@ "testing"   "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/testlog" @@ -257,7 +258,7 @@ var data []byte var err error if e.finalState != nil && e.finalState.Step <= i { // Requesting a trace index past the end of the trace - proofFile = filepath.Join(dir, utils.FinalState) + proofFile = filepath.Join(dir, vm.FinalState) data, err = json.Marshal(e.finalState) if err != nil { return err
diff --git OP/op-challenger/game/fault/trace/outputs/output_asterisc.go CELO/op-challenger/game/fault/trace/outputs/output_asterisc.go index a6bbf39e7288fc290f3beea3c419f70acecb0c5d..ac129dbb26c82c791c5b79529ceefcabcfdd6e62 100644 --- OP/op-challenger/game/fault/trace/outputs/output_asterisc.go +++ CELO/op-challenger/game/fault/trace/outputs/output_asterisc.go @@ -5,12 +5,12 @@ "context" "fmt" "path/filepath"   - "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/asterisc" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -21,7 +21,7 @@ func NewOutputAsteriscTraceAccessor( logger log.Logger, m metrics.Metricer, - cfg *config.Config, + cfg vm.Config, l2Client utils.L2HeaderSource, prestateProvider types.PrestateProvider, asteriscPrestate string,
diff --git OP/op-challenger/game/fault/trace/outputs/output_cannon.go CELO/op-challenger/game/fault/trace/outputs/output_cannon.go index 7a2b3d36a975a2fdc3c73ffc1aaf13dc9beba01b..ecc710380bfb82e4ec9d7d7e39112acbb8c1cc57 100644 --- OP/op-challenger/game/fault/trace/outputs/output_cannon.go +++ CELO/op-challenger/game/fault/trace/outputs/output_cannon.go @@ -5,12 +5,12 @@ "context" "fmt" "path/filepath"   - "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -21,7 +21,7 @@ func NewOutputCannonTraceAccessor( logger log.Logger, m metrics.Metricer, - cfg *config.Config, + cfg vm.Config, l2Client utils.L2HeaderSource, prestateProvider types.PrestateProvider, cannonPrestate string,
diff --git OP/op-challenger/game/fault/trace/vm/executor.go CELO/op-challenger/game/fault/trace/vm/executor.go new file mode 100644 index 0000000000000000000000000000000000000000..564ef6ac8d20105afbbf90cae3eff662db8a255b --- /dev/null +++ CELO/op-challenger/game/fault/trace/vm/executor.go @@ -0,0 +1,126 @@ +package vm + +import ( + "context" + "fmt" + "math" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum/go-ethereum/log" +) + +type Metricer interface { + RecordVmExecutionTime(vmType string, t time.Duration) +} + +type Config struct { + VmType string + L1 string + L1Beacon string + L2 string + VmBin string // Path to the vm executable to run when generating trace data + Server string // Path to the executable that provides the pre-image oracle server + Network string + RollupConfigPath string + L2GenesisPath string + SnapshotFreq uint // Frequency of snapshots to create when executing (in VM instructions) + InfoFreq uint // Frequency of progress log messages (in VM instructions) +} + +type Executor struct { + cfg Config + logger log.Logger + metrics Metricer + absolutePreState string + inputs utils.LocalGameInputs + selectSnapshot SnapshotSelect + cmdExecutor CmdExecutor +} + +func NewExecutor(logger log.Logger, m Metricer, cfg Config, prestate string, inputs utils.LocalGameInputs) *Executor { + return &Executor{ + cfg: cfg, + logger: logger, + metrics: m, + inputs: inputs, + absolutePreState: prestate, + selectSnapshot: FindStartingSnapshot, + cmdExecutor: RunCmd, + } +} + +// GenerateProof executes vm to generate a proof at the specified trace index. +// The proof is stored at the specified directory. +func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) error { + return e.DoGenerateProof(ctx, dir, i, i) +} + +// DoGenerateProof executes vm from the specified starting trace index until the end trace index. +// The proof is stored at the specified directory. +func (e *Executor) DoGenerateProof(ctx context.Context, dir string, begin uint64, end uint64, extraVmArgs ...string) error { + snapshotDir := filepath.Join(dir, SnapsDir) + start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin) + if err != nil { + return fmt.Errorf("find starting snapshot: %w", err) + } + proofDir := filepath.Join(dir, utils.ProofsDir) + dataDir := PreimageDir(dir) + lastGeneratedState := filepath.Join(dir, FinalState) + args := []string{ + "run", + "--input", start, + "--output", lastGeneratedState, + "--meta", "", + "--info-at", "%" + strconv.FormatUint(uint64(e.cfg.InfoFreq), 10), + "--proof-at", "=" + strconv.FormatUint(end, 10), + "--proof-fmt", filepath.Join(proofDir, "%d.json.gz"), + "--snapshot-at", "%" + strconv.FormatUint(uint64(e.cfg.SnapshotFreq), 10), + "--snapshot-fmt", filepath.Join(snapshotDir, "%d.json.gz"), + } + if end < math.MaxUint64 { + args = append(args, "--stop-at", "="+strconv.FormatUint(end+1, 10)) + } + args = append(args, extraVmArgs...) + args = append(args, + "--", + e.cfg.Server, "--server", + "--l1", e.cfg.L1, + "--l1.beacon", e.cfg.L1Beacon, + "--l2", e.cfg.L2, + "--datadir", dataDir, + "--l1.head", e.inputs.L1Head.Hex(), + "--l2.head", e.inputs.L2Head.Hex(), + "--l2.outputroot", e.inputs.L2OutputRoot.Hex(), + "--l2.claim", e.inputs.L2Claim.Hex(), + "--l2.blocknumber", e.inputs.L2BlockNumber.Text(10), + ) + if e.cfg.Network != "" { + args = append(args, "--network", e.cfg.Network) + } + if e.cfg.RollupConfigPath != "" { + args = append(args, "--rollup.config", e.cfg.RollupConfigPath) + } + if e.cfg.L2GenesisPath != "" { + args = append(args, "--l2.genesis", e.cfg.L2GenesisPath) + } + + if err := os.MkdirAll(snapshotDir, 0755); err != nil { + return fmt.Errorf("could not create snapshot directory %v: %w", snapshotDir, err) + } + if err := os.MkdirAll(dataDir, 0755); err != nil { + return fmt.Errorf("could not create preimage cache directory %v: %w", dataDir, err) + } + if err := os.MkdirAll(proofDir, 0755); err != nil { + return fmt.Errorf("could not create proofs directory %v: %w", proofDir, err) + } + e.logger.Info("Generating trace", "proof", end, "cmd", e.cfg.VmBin, "args", strings.Join(args, ", ")) + execStart := time.Now() + err = e.cmdExecutor(ctx, e.logger.New("proof", end), e.cfg.VmBin, args...) + e.metrics.RecordVmExecutionTime(e.cfg.VmType, time.Since(execStart)) + return err +}
diff --git OP/op-challenger/game/fault/trace/asterisc/executor_test.go CELO/op-challenger/game/fault/trace/vm/executor_test.go rename from op-challenger/game/fault/trace/asterisc/executor_test.go rename to op-challenger/game/fault/trace/vm/executor_test.go index 7ce44f304375184fb17b40c5a04ef709d83a6365..00078bd2078e958157897d3b4144fc97c1c8b760 100644 --- OP/op-challenger/game/fault/trace/asterisc/executor_test.go +++ CELO/op-challenger/game/fault/trace/vm/executor_test.go @@ -1,4 +1,4 @@ -package asterisc +package vm   import ( "context" @@ -6,8 +6,8 @@ "math" "math/big" "path/filepath" "testing" + "time"   - "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/testlog" @@ -20,13 +20,18 @@ func TestGenerateProof(t *testing.T) { input := "starting.json" tempDir := t.TempDir() dir := filepath.Join(tempDir, "gameDir") - cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", "http://localhost:9096", "http://localhost:9095", tempDir, config.TraceTypeAsterisc) - cfg.L2Rpc = "http://localhost:9999" + cfg := Config{ + VmType: "test", + L1: "http://localhost:8888", + L1Beacon: "http://localhost:9000", + L2: "http://localhost:9999", + VmBin: "./bin/testvm", + Server: "./bin/testserver", + Network: "op-test", + SnapshotFreq: 500, + InfoFreq: 900, + } prestate := "pre.json" - cfg.AsteriscBin = "./bin/asterisc" - cfg.AsteriscServer = "./bin/op-program" - cfg.AsteriscSnapshotFreq = 500 - cfg.AsteriscInfoFreq = 900   inputs := utils.LocalGameInputs{ L1Head: common.Hash{0x11}, @@ -35,9 +40,9 @@ L2OutputRoot: common.Hash{0x33}, L2Claim: common.Hash{0x44}, L2BlockNumber: big.NewInt(3333), } - captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) { - m := &asteriscDurationMetrics{} - executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, &cfg, prestate, inputs) + captureExec := func(t *testing.T, cfg Config, proofAt uint64) (string, string, map[string]string) { + m := &stubVmMetrics{} + executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, cfg, prestate, inputs) executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) { return input, nil } @@ -49,7 +54,7 @@ binary = b subcommand = a[0] for i := 1; i < len(a); { if a[i] == "--" { - // Skip over the divider between asterisc and server program + // Skip over the divider between vm and server program i += 1 continue } @@ -60,24 +65,24 @@ return nil } err := executor.GenerateProof(context.Background(), dir, proofAt) require.NoError(t, err) - require.Equal(t, 1, m.executionTimeRecordCount, "Should record asterisc execution time") + require.Equal(t, 1, m.executionTimeRecordCount, "Should record vm execution time") return binary, subcommand, args }   t.Run("Network", func(t *testing.T) { - cfg.AsteriscNetwork = "mainnet" - cfg.AsteriscRollupConfigPath = "" - cfg.AsteriscL2GenesisPath = "" + cfg.Network = "mainnet" + cfg.RollupConfigPath = "" + cfg.L2GenesisPath = "" binary, subcommand, args := captureExec(t, cfg, 150_000_000) - require.DirExists(t, filepath.Join(dir, utils.PreimagesDir)) - require.DirExists(t, filepath.Join(dir, proofsDir)) - require.DirExists(t, filepath.Join(dir, utils.SnapsDir)) - require.Equal(t, cfg.AsteriscBin, binary) + require.DirExists(t, filepath.Join(dir, PreimagesDir)) + require.DirExists(t, filepath.Join(dir, utils.ProofsDir)) + require.DirExists(t, filepath.Join(dir, SnapsDir)) + require.Equal(t, cfg.VmBin, binary) require.Equal(t, "run", subcommand) require.Equal(t, input, args["--input"]) require.Contains(t, args, "--meta") require.Equal(t, "", args["--meta"]) - require.Equal(t, filepath.Join(dir, utils.FinalState), args["--output"]) + require.Equal(t, filepath.Join(dir, FinalState), args["--output"]) require.Equal(t, "=150000000", args["--proof-at"]) require.Equal(t, "=150000001", args["--stop-at"]) require.Equal(t, "%500", args["--snapshot-at"]) @@ -85,14 +90,14 @@ require.Equal(t, "%900", args["--info-at"]) // Slight quirk of how we pair off args // The server binary winds up as the key and the first arg --server as the value which has no value // Then everything else pairs off correctly again - require.Equal(t, "--server", args[cfg.AsteriscServer]) - require.Equal(t, cfg.L1EthRpc, args["--l1"]) + require.Equal(t, "--server", args[cfg.Server]) + require.Equal(t, cfg.L1, args["--l1"]) require.Equal(t, cfg.L1Beacon, args["--l1.beacon"]) - require.Equal(t, cfg.L2Rpc, args["--l2"]) - require.Equal(t, filepath.Join(dir, utils.PreimagesDir), args["--datadir"]) - require.Equal(t, filepath.Join(dir, proofsDir, "%d.json.gz"), args["--proof-fmt"]) - require.Equal(t, filepath.Join(dir, utils.SnapsDir, "%d.json.gz"), args["--snapshot-fmt"]) - require.Equal(t, cfg.AsteriscNetwork, args["--network"]) + require.Equal(t, cfg.L2, args["--l2"]) + require.Equal(t, filepath.Join(dir, PreimagesDir), args["--datadir"]) + require.Equal(t, filepath.Join(dir, utils.ProofsDir, "%d.json.gz"), args["--proof-fmt"]) + require.Equal(t, filepath.Join(dir, SnapsDir, "%d.json.gz"), args["--snapshot-fmt"]) + require.Equal(t, cfg.Network, args["--network"]) require.NotContains(t, args, "--rollup.config") require.NotContains(t, args, "--l2.genesis")   @@ -105,19 +110,19 @@ require.Equal(t, "3333", args["--l2.blocknumber"]) })   t.Run("RollupAndGenesis", func(t *testing.T) { - cfg.AsteriscNetwork = "" - cfg.AsteriscRollupConfigPath = "rollup.json" - cfg.AsteriscL2GenesisPath = "genesis.json" + cfg.Network = "" + cfg.RollupConfigPath = "rollup.json" + cfg.L2GenesisPath = "genesis.json" _, _, args := captureExec(t, cfg, 150_000_000) require.NotContains(t, args, "--network") - require.Equal(t, cfg.AsteriscRollupConfigPath, args["--rollup.config"]) - require.Equal(t, cfg.AsteriscL2GenesisPath, args["--l2.genesis"]) + require.Equal(t, cfg.RollupConfigPath, args["--rollup.config"]) + require.Equal(t, cfg.L2GenesisPath, args["--l2.genesis"]) })   t.Run("NoStopAtWhenProofIsMaxUInt", func(t *testing.T) { - cfg.AsteriscNetwork = "mainnet" - cfg.AsteriscRollupConfigPath = "rollup.json" - cfg.AsteriscL2GenesisPath = "genesis.json" + cfg.Network = "mainnet" + cfg.RollupConfigPath = "rollup.json" + cfg.L2GenesisPath = "genesis.json" _, _, args := captureExec(t, cfg, math.MaxUint64) // stop-at would need to be one more than the proof step which would overflow back to 0 // so expect that it will be omitted. We'll ultimately want asterisc to execute until the program exits. @@ -125,11 +130,11 @@ require.NotContains(t, args, "--stop-at") }) }   -type asteriscDurationMetrics struct { +type stubVmMetrics struct { metrics.NoopMetricsImpl executionTimeRecordCount int }   -func (c *asteriscDurationMetrics) RecordAsteriscExecutionTime(_ float64) { +func (c *stubVmMetrics) RecordVmExecutionTime(_ string, _ time.Duration) { c.executionTimeRecordCount++ }
diff --git OP/op-challenger/game/fault/trace/utils/executor.go CELO/op-challenger/game/fault/trace/vm/prestates.go rename from op-challenger/game/fault/trace/utils/executor.go rename to op-challenger/game/fault/trace/vm/prestates.go index f3c5feac8311aa4f5510a0563f4ee23eb8e4c8b7..c51dda7de1e5bf1346d1657be0d235ad4f473255 100644 --- OP/op-challenger/game/fault/trace/utils/executor.go +++ CELO/op-challenger/game/fault/trace/vm/prestates.go @@ -1,4 +1,4 @@ -package utils +package vm   import ( "context" @@ -10,7 +10,7 @@ "path/filepath" "regexp" "strconv"   - oplog "github.com/ethereum-optimism/optimism/op-service/log" + log2 "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum/go-ethereum/log" )   @@ -31,10 +31,10 @@ }   func RunCmd(ctx context.Context, l log.Logger, binary string, args ...string) error { cmd := exec.CommandContext(ctx, binary, args...) - stdOut := oplog.NewWriter(l, log.LevelInfo) + stdOut := log2.NewWriter(l, log.LevelInfo) defer stdOut.Close() // Keep stdErr at info level because FPVM uses stderr for progress messages - stdErr := oplog.NewWriter(l, log.LevelInfo) + stdErr := log2.NewWriter(l, log.LevelInfo) defer stdErr.Close() cmd.Stdout = stdOut cmd.Stderr = stdErr
diff --git OP/op-conductor/conductor/service.go CELO/op-conductor/conductor/service.go index 02c46a468bc78bb806d6fbc9a04e1cb4e87cbea5..a1ccef871538aa5426392d532c0e418f695a30a1 100644 --- OP/op-conductor/conductor/service.go +++ CELO/op-conductor/conductor/service.go @@ -311,7 +311,7 @@ // Start implements cliapp.Lifecycle. func (oc *OpConductor) Start(ctx context.Context) error { oc.log.Info("starting OpConductor")   - if err := oc.hmon.Start(); err != nil { + if err := oc.hmon.Start(ctx); err != nil { return errors.Wrap(err, "failed to start health monitor") }   @@ -453,18 +453,18 @@ return oc.cons.LeaderWithID() }   // AddServerAsVoter adds a server as a voter to the cluster. -func (oc *OpConductor) AddServerAsVoter(_ context.Context, id string, addr string) error { - return oc.cons.AddVoter(id, addr) +func (oc *OpConductor) AddServerAsVoter(_ context.Context, id string, addr string, version uint64) error { + return oc.cons.AddVoter(id, addr, version) }   // AddServerAsNonvoter adds a server as a non-voter to the cluster. non-voter will not participate in leader election. -func (oc *OpConductor) AddServerAsNonvoter(_ context.Context, id string, addr string) error { - return oc.cons.AddNonVoter(id, addr) +func (oc *OpConductor) AddServerAsNonvoter(_ context.Context, id string, addr string, version uint64) error { + return oc.cons.AddNonVoter(id, addr, version) }   // RemoveServer removes a server from the cluster. -func (oc *OpConductor) RemoveServer(_ context.Context, id string) error { - return oc.cons.RemoveServer(id) +func (oc *OpConductor) RemoveServer(_ context.Context, id string, version uint64) error { + return oc.cons.RemoveServer(id, version) }   // TransferLeader transfers leadership to another server. @@ -488,7 +488,7 @@ return oc.healthy.Load() }   // ClusterMembership returns current cluster's membership information. -func (oc *OpConductor) ClusterMembership(_ context.Context) ([]*consensus.ServerInfo, error) { +func (oc *OpConductor) ClusterMembership(_ context.Context) (*consensus.ClusterMembership, error) { return oc.cons.ClusterMembership() }
diff --git OP/op-conductor/conductor/service_test.go CELO/op-conductor/conductor/service_test.go index f6d84d6db5fc64e2d6cf632270e74d0e79878018..4e19925baa4b163fbc416b1e896d60276e811867 100644 --- OP/op-conductor/conductor/service_test.go +++ CELO/op-conductor/conductor/service_test.go @@ -122,7 +122,7 @@ conductor.retryBackoff = func() time.Duration { return 0 } // disable retry backoff for tests s.conductor = conductor   s.healthUpdateCh = make(chan error, 1) - s.hmon.EXPECT().Start().Return(nil) + s.hmon.EXPECT().Start(mock.Anything).Return(nil) s.conductor.healthUpdateCh = s.healthUpdateCh   s.leaderUpdateCh = make(chan bool, 1)
diff --git OP/op-conductor/consensus/iface.go CELO/op-conductor/consensus/iface.go index 15096c2e8ae122e0c9f56bed36bf1a949d31e54f..69b9506c50b26656788e10d3184445f0860ec689 100644 --- OP/op-conductor/consensus/iface.go +++ CELO/op-conductor/consensus/iface.go @@ -25,6 +25,12 @@ } return "ServerSuffrage" }   +// ClusterMembership defines a versioned list of servers in the cluster. +type ClusterMembership struct { + Servers []ServerInfo `json:"servers"` + Version uint64 `json:"version"` +} + // ServerInfo defines the server information. type ServerInfo struct { ID string `json:"id"` @@ -37,13 +43,17 @@ // //go:generate mockery --name Consensus --output mocks/ --with-expecter=true type Consensus interface { // AddVoter adds a voting member into the cluster, voter is eligible to become leader. - AddVoter(id, addr string) error + // If version is non-zero, this will only be applied if the current cluster version matches the expected version. + AddVoter(id, addr string, version uint64) error // AddNonVoter adds a non-voting member into the cluster, non-voter is not eligible to become leader. - AddNonVoter(id, addr string) error + // If version is non-zero, this will only be applied if the current cluster version matches the expected version. + AddNonVoter(id, addr string, version uint64) error // DemoteVoter demotes a voting member into a non-voting member, if leader is being demoted, it will cause a new leader election. - DemoteVoter(id string) error + // If version is non-zero, this will only be applied if the current cluster version matches the expected version. + DemoteVoter(id string, version uint64) error // RemoveServer removes a member (both voter or non-voter) from the cluster, if leader is being removed, it will cause a new leader election. - RemoveServer(id string) error + // If version is non-zero, this will only be applied if the current cluster version matches the expected version. + RemoveServer(id string, version uint64) error // LeaderCh returns a channel that will be notified when leadership status changes (true = leader, false = follower) LeaderCh() <-chan bool // Leader returns if it is the leader of the cluster. @@ -56,8 +66,8 @@ // TransferLeader triggers leadership transfer to another member in the cluster. TransferLeader() error // TransferLeaderTo triggers leadership transfer to a specific member in the cluster. TransferLeaderTo(id, addr string) error - // ClusterMembership returns the current cluster membership configuration. - ClusterMembership() ([]*ServerInfo, error) + // ClusterMembership returns the current cluster membership configuration and associated version. + ClusterMembership() (*ClusterMembership, error)   // CommitPayload commits latest unsafe payload to the FSM in a strongly consistent fashion. CommitUnsafePayload(payload *eth.ExecutionPayloadEnvelope) error
diff --git OP/op-conductor/consensus/mocks/Consensus.go CELO/op-conductor/consensus/mocks/Consensus.go index 02d65869c06a8d4837ebcaca1d08ef095d6d53df..ca1397a690e1f93f66f5df38ad3f7e613eae46eb 100644 --- OP/op-conductor/consensus/mocks/Consensus.go +++ CELO/op-conductor/consensus/mocks/Consensus.go @@ -22,17 +22,17 @@ func (_m *Consensus) EXPECT() *Consensus_Expecter { return &Consensus_Expecter{mock: &_m.Mock} }   -// AddNonVoter provides a mock function with given fields: id, addr -func (_m *Consensus) AddNonVoter(id string, addr string) error { - ret := _m.Called(id, addr) +// AddNonVoter provides a mock function with given fields: id, addr, version +func (_m *Consensus) AddNonVoter(id string, addr string, version uint64) error { + ret := _m.Called(id, addr, version)   if len(ret) == 0 { panic("no return value specified for AddNonVoter") }   var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(id, addr) + if rf, ok := ret.Get(0).(func(string, string, uint64) error); ok { + r0 = rf(id, addr, version) } else { r0 = ret.Error(0) } @@ -48,13 +48,14 @@ // AddNonVoter is a helper method to define mock.On call // - id string // - addr string -func (_e *Consensus_Expecter) AddNonVoter(id interface{}, addr interface{}) *Consensus_AddNonVoter_Call { - return &Consensus_AddNonVoter_Call{Call: _e.mock.On("AddNonVoter", id, addr)} +// - version uint64 +func (_e *Consensus_Expecter) AddNonVoter(id interface{}, addr interface{}, version interface{}) *Consensus_AddNonVoter_Call { + return &Consensus_AddNonVoter_Call{Call: _e.mock.On("AddNonVoter", id, addr, version)} }   -func (_c *Consensus_AddNonVoter_Call) Run(run func(id string, addr string)) *Consensus_AddNonVoter_Call { +func (_c *Consensus_AddNonVoter_Call) Run(run func(id string, addr string, version uint64)) *Consensus_AddNonVoter_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) + run(args[0].(string), args[1].(string), args[2].(uint64)) }) return _c } @@ -64,22 +65,22 @@ _c.Call.Return(_a0) return _c }   -func (_c *Consensus_AddNonVoter_Call) RunAndReturn(run func(string, string) error) *Consensus_AddNonVoter_Call { +func (_c *Consensus_AddNonVoter_Call) RunAndReturn(run func(string, string, uint64) error) *Consensus_AddNonVoter_Call { _c.Call.Return(run) return _c }   -// AddVoter provides a mock function with given fields: id, addr -func (_m *Consensus) AddVoter(id string, addr string) error { - ret := _m.Called(id, addr) +// AddVoter provides a mock function with given fields: id, addr, version +func (_m *Consensus) AddVoter(id string, addr string, version uint64) error { + ret := _m.Called(id, addr, version)   if len(ret) == 0 { panic("no return value specified for AddVoter") }   var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(id, addr) + if rf, ok := ret.Get(0).(func(string, string, uint64) error); ok { + r0 = rf(id, addr, version) } else { r0 = ret.Error(0) } @@ -95,13 +96,14 @@ // AddVoter is a helper method to define mock.On call // - id string // - addr string -func (_e *Consensus_Expecter) AddVoter(id interface{}, addr interface{}) *Consensus_AddVoter_Call { - return &Consensus_AddVoter_Call{Call: _e.mock.On("AddVoter", id, addr)} +// - version uint64 +func (_e *Consensus_Expecter) AddVoter(id interface{}, addr interface{}, version interface{}) *Consensus_AddVoter_Call { + return &Consensus_AddVoter_Call{Call: _e.mock.On("AddVoter", id, addr, version)} }   -func (_c *Consensus_AddVoter_Call) Run(run func(id string, addr string)) *Consensus_AddVoter_Call { +func (_c *Consensus_AddVoter_Call) Run(run func(id string, addr string, version uint64)) *Consensus_AddVoter_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) + run(args[0].(string), args[1].(string), args[2].(uint64)) }) return _c } @@ -111,29 +113,29 @@ _c.Call.Return(_a0) return _c }   -func (_c *Consensus_AddVoter_Call) RunAndReturn(run func(string, string) error) *Consensus_AddVoter_Call { +func (_c *Consensus_AddVoter_Call) RunAndReturn(run func(string, string, uint64) error) *Consensus_AddVoter_Call { _c.Call.Return(run) return _c }   // ClusterMembership provides a mock function with given fields: -func (_m *Consensus) ClusterMembership() ([]*consensus.ServerInfo, error) { +func (_m *Consensus) ClusterMembership() (*consensus.ClusterMembership, error) { ret := _m.Called()   if len(ret) == 0 { panic("no return value specified for ClusterMembership") }   - var r0 []*consensus.ServerInfo + var r0 *consensus.ClusterMembership var r1 error - if rf, ok := ret.Get(0).(func() ([]*consensus.ServerInfo, error)); ok { + if rf, ok := ret.Get(0).(func() (*consensus.ClusterMembership, error)); ok { return rf() } - if rf, ok := ret.Get(0).(func() []*consensus.ServerInfo); ok { + if rf, ok := ret.Get(0).(func() *consensus.ClusterMembership); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*consensus.ServerInfo) + r0 = ret.Get(0).(*consensus.ClusterMembership) } }   @@ -163,12 +165,12 @@ }) return _c }   -func (_c *Consensus_ClusterMembership_Call) Return(_a0 []*consensus.ServerInfo, _a1 error) *Consensus_ClusterMembership_Call { +func (_c *Consensus_ClusterMembership_Call) Return(_a0 *consensus.ClusterMembership, _a1 error) *Consensus_ClusterMembership_Call { _c.Call.Return(_a0, _a1) return _c }   -func (_c *Consensus_ClusterMembership_Call) RunAndReturn(run func() ([]*consensus.ServerInfo, error)) *Consensus_ClusterMembership_Call { +func (_c *Consensus_ClusterMembership_Call) RunAndReturn(run func() (*consensus.ClusterMembership, error)) *Consensus_ClusterMembership_Call { _c.Call.Return(run) return _c } @@ -219,17 +221,17 @@ _c.Call.Return(run) return _c }   -// DemoteVoter provides a mock function with given fields: id -func (_m *Consensus) DemoteVoter(id string) error { - ret := _m.Called(id) +// DemoteVoter provides a mock function with given fields: id, version +func (_m *Consensus) DemoteVoter(id string, version uint64) error { + ret := _m.Called(id, version)   if len(ret) == 0 { panic("no return value specified for DemoteVoter") }   var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(id) + if rf, ok := ret.Get(0).(func(string, uint64) error); ok { + r0 = rf(id, version) } else { r0 = ret.Error(0) } @@ -244,13 +246,14 @@ }   // DemoteVoter is a helper method to define mock.On call // - id string -func (_e *Consensus_Expecter) DemoteVoter(id interface{}) *Consensus_DemoteVoter_Call { - return &Consensus_DemoteVoter_Call{Call: _e.mock.On("DemoteVoter", id)} +// - version uint64 +func (_e *Consensus_Expecter) DemoteVoter(id interface{}, version interface{}) *Consensus_DemoteVoter_Call { + return &Consensus_DemoteVoter_Call{Call: _e.mock.On("DemoteVoter", id, version)} }   -func (_c *Consensus_DemoteVoter_Call) Run(run func(id string)) *Consensus_DemoteVoter_Call { +func (_c *Consensus_DemoteVoter_Call) Run(run func(id string, version uint64)) *Consensus_DemoteVoter_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + run(args[0].(string), args[1].(uint64)) }) return _c } @@ -260,7 +263,7 @@ _c.Call.Return(_a0) return _c }   -func (_c *Consensus_DemoteVoter_Call) RunAndReturn(run func(string) error) *Consensus_DemoteVoter_Call { +func (_c *Consensus_DemoteVoter_Call) RunAndReturn(run func(string, uint64) error) *Consensus_DemoteVoter_Call { _c.Call.Return(run) return _c } @@ -461,17 +464,17 @@ _c.Call.Return(run) return _c }   -// RemoveServer provides a mock function with given fields: id -func (_m *Consensus) RemoveServer(id string) error { - ret := _m.Called(id) +// RemoveServer provides a mock function with given fields: id, version +func (_m *Consensus) RemoveServer(id string, version uint64) error { + ret := _m.Called(id, version)   if len(ret) == 0 { panic("no return value specified for RemoveServer") }   var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(id) + if rf, ok := ret.Get(0).(func(string, uint64) error); ok { + r0 = rf(id, version) } else { r0 = ret.Error(0) } @@ -486,13 +489,14 @@ }   // RemoveServer is a helper method to define mock.On call // - id string -func (_e *Consensus_Expecter) RemoveServer(id interface{}) *Consensus_RemoveServer_Call { - return &Consensus_RemoveServer_Call{Call: _e.mock.On("RemoveServer", id)} +// - version uint64 +func (_e *Consensus_Expecter) RemoveServer(id interface{}, version interface{}) *Consensus_RemoveServer_Call { + return &Consensus_RemoveServer_Call{Call: _e.mock.On("RemoveServer", id, version)} }   -func (_c *Consensus_RemoveServer_Call) Run(run func(id string)) *Consensus_RemoveServer_Call { +func (_c *Consensus_RemoveServer_Call) Run(run func(id string, version uint64)) *Consensus_RemoveServer_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + run(args[0].(string), args[1].(uint64)) }) return _c } @@ -502,7 +506,7 @@ _c.Call.Return(_a0) return _c }   -func (_c *Consensus_RemoveServer_Call) RunAndReturn(run func(string) error) *Consensus_RemoveServer_Call { +func (_c *Consensus_RemoveServer_Call) RunAndReturn(run func(string, uint64) error) *Consensus_RemoveServer_Call { _c.Call.Return(run) return _c }
diff --git OP/op-conductor/consensus/raft.go CELO/op-conductor/consensus/raft.go index 2c3f79fe29469655dd4c070e0ec4929525f3d164..b80c39be06c75994912a9b47fa6194b90119141d 100644 --- OP/op-conductor/consensus/raft.go +++ CELO/op-conductor/consensus/raft.go @@ -112,27 +112,36 @@ }, nil }   // AddNonVoter implements Consensus, it tries to add a non-voting member into the cluster. -func (rc *RaftConsensus) AddNonVoter(id string, addr string) error { - if err := rc.r.AddNonvoter(raft.ServerID(id), raft.ServerAddress(addr), 0, defaultTimeout).Error(); err != nil { - rc.log.Error("failed to add non-voter", "id", id, "addr", addr, "err", err) +func (rc *RaftConsensus) AddNonVoter(id string, addr string, version uint64) error { + if err := rc.r.AddNonvoter(raft.ServerID(id), raft.ServerAddress(addr), version, defaultTimeout).Error(); err != nil { + rc.log.Error("failed to add non-voter", "id", id, "addr", addr, "version", version, "err", err) return err } return nil }   // AddVoter implements Consensus, it tries to add a voting member into the cluster. -func (rc *RaftConsensus) AddVoter(id string, addr string) error { - if err := rc.r.AddVoter(raft.ServerID(id), raft.ServerAddress(addr), 0, defaultTimeout).Error(); err != nil { - rc.log.Error("failed to add voter", "id", id, "addr", addr, "err", err) +func (rc *RaftConsensus) AddVoter(id string, addr string, version uint64) error { + if err := rc.r.AddVoter(raft.ServerID(id), raft.ServerAddress(addr), version, defaultTimeout).Error(); err != nil { + rc.log.Error("failed to add voter", "id", id, "addr", addr, "version", version, "err", err) return err } return nil }   // DemoteVoter implements Consensus, it tries to demote a voting member into a non-voting member in the cluster. -func (rc *RaftConsensus) DemoteVoter(id string) error { - if err := rc.r.DemoteVoter(raft.ServerID(id), 0, defaultTimeout).Error(); err != nil { - rc.log.Error("failed to demote voter", "id", id, "err", err) +func (rc *RaftConsensus) DemoteVoter(id string, version uint64) error { + if err := rc.r.DemoteVoter(raft.ServerID(id), version, defaultTimeout).Error(); err != nil { + rc.log.Error("failed to demote voter", "id", id, "version", version, "err", err) + return err + } + return nil +} + +// RemoveServer implements Consensus, it tries to remove a member (both voter or non-voter) from the cluster, if leader is being removed, it will cause a new leader election. +func (rc *RaftConsensus) RemoveServer(id string, version uint64) error { + if err := rc.r.RemoveServer(raft.ServerID(id), version, defaultTimeout).Error(); err != nil { + rc.log.Error("failed to remove voter", "id", id, "version", version, "err", err) return err } return nil @@ -156,15 +165,6 @@ // LeaderCh implements Consensus, it returns a channel that will be notified when leadership status changes (true = leader, false = follower). func (rc *RaftConsensus) LeaderCh() <-chan bool { return rc.r.LeaderCh() -} - -// RemoveServer implements Consensus, it tries to remove a member (both voter or non-voter) from the cluster, if leader is being removed, it will cause a new leader election. -func (rc *RaftConsensus) RemoveServer(id string) error { - if err := rc.r.RemoveServer(raft.ServerID(id), 0, defaultTimeout).Error(); err != nil { - rc.log.Error("failed to remove voter", "id", id, "err", err) - return err - } - return nil }   // ServerID implements Consensus, it returns the server ID of the current server. @@ -232,19 +232,22 @@ return rc.unsafeTracker.UnsafeHead(), nil }   // ClusterMembership implements Consensus, it returns the current cluster membership configuration. -func (rc *RaftConsensus) ClusterMembership() ([]*ServerInfo, error) { +func (rc *RaftConsensus) ClusterMembership() (*ClusterMembership, error) { var future raft.ConfigurationFuture if future = rc.r.GetConfiguration(); future.Error() != nil { return nil, future.Error() }   - var servers []*ServerInfo + var servers []ServerInfo for _, srv := range future.Configuration().Servers { - servers = append(servers, &ServerInfo{ + servers = append(servers, ServerInfo{ ID: string(srv.ID), Addr: string(srv.Address), Suffrage: ServerSuffrage(srv.Suffrage), }) } - return servers, nil + return &ClusterMembership{ + Servers: servers, + Version: future.Index(), + }, nil }
diff --git OP/op-conductor/health/mocks/HealthMonitor.go CELO/op-conductor/health/mocks/HealthMonitor.go index de85b716f7393e0994a46207598f57b735acb36e..0cc61c62a9d4e4a9ccdaa8e349d08fd3d379efd6 100644 --- OP/op-conductor/health/mocks/HealthMonitor.go +++ CELO/op-conductor/health/mocks/HealthMonitor.go @@ -2,7 +2,11 @@ // Code generated by mockery v2.39.1. DO NOT EDIT.   package mocks   -import mock "github.com/stretchr/testify/mock" +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +)   // HealthMonitor is an autogenerated mock type for the HealthMonitor type type HealthMonitor struct { @@ -17,17 +21,17 @@ func (_m *HealthMonitor) EXPECT() *HealthMonitor_Expecter { return &HealthMonitor_Expecter{mock: &_m.Mock} }   -// Start provides a mock function with given fields: -func (_m *HealthMonitor) Start() error { - ret := _m.Called() +// Start provides a mock function with given fields: ctx +func (_m *HealthMonitor) Start(ctx context.Context) error { + ret := _m.Called(ctx)   if len(ret) == 0 { panic("no return value specified for Start") }   var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) } else { r0 = ret.Error(0) } @@ -41,13 +45,14 @@ *mock.Call }   // Start is a helper method to define mock.On call -func (_e *HealthMonitor_Expecter) Start() *HealthMonitor_Start_Call { - return &HealthMonitor_Start_Call{Call: _e.mock.On("Start")} +// - ctx context.Context +func (_e *HealthMonitor_Expecter) Start(ctx interface{}) *HealthMonitor_Start_Call { + return &HealthMonitor_Start_Call{Call: _e.mock.On("Start", ctx)} }   -func (_c *HealthMonitor_Start_Call) Run(run func()) *HealthMonitor_Start_Call { +func (_c *HealthMonitor_Start_Call) Run(run func(ctx context.Context)) *HealthMonitor_Start_Call { _c.Call.Run(func(args mock.Arguments) { - run() + run(args[0].(context.Context)) }) return _c } @@ -57,7 +62,7 @@ _c.Call.Return(_a0) return _c }   -func (_c *HealthMonitor_Start_Call) RunAndReturn(run func() error) *HealthMonitor_Start_Call { +func (_c *HealthMonitor_Start_Call) RunAndReturn(run func(context.Context) error) *HealthMonitor_Start_Call { _c.Call.Return(run) return _c }
diff --git OP/op-conductor/health/monitor.go CELO/op-conductor/health/monitor.go index 8ae1fe166bdcf3baaefb731a136c805b022f3b2c..d2b28ffab7d918d20477b8efe758c056cf62111e 100644 --- OP/op-conductor/health/monitor.go +++ CELO/op-conductor/health/monitor.go @@ -26,7 +26,7 @@ type HealthMonitor interface { // Subscribe returns a channel that will be notified for every health check. Subscribe() <-chan error // Start starts the health check. - Start() error + Start(ctx context.Context) error // Stop stops the health check. Stop() error } @@ -39,7 +39,6 @@ func NewSequencerHealthMonitor(log log.Logger, metrics metrics.Metricer, interval, unsafeInterval, safeInterval, minPeerCount uint64, safeEnabled bool, rollupCfg *rollup.Config, node dial.RollupClientInterface, p2p p2p.API) HealthMonitor { return &SequencerHealthMonitor{ log: log, metrics: metrics, - done: make(chan struct{}), interval: interval, healthUpdateCh: make(chan error), rollupCfg: rollupCfg, @@ -57,7 +56,7 @@ // SequencerHealthMonitor monitors sequencer health. type SequencerHealthMonitor struct { log log.Logger metrics metrics.Metricer - done chan struct{} + cancel context.CancelFunc wg sync.WaitGroup   rollupCfg *rollup.Config @@ -79,10 +78,13 @@ var _ HealthMonitor = (*SequencerHealthMonitor)(nil)   // Start implements HealthMonitor. -func (hm *SequencerHealthMonitor) Start() error { +func (hm *SequencerHealthMonitor) Start(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + hm.cancel = cancel + hm.log.Info("starting health monitor") hm.wg.Add(1) - go hm.loop() + go hm.loop(ctx)   hm.log.Info("health monitor started") return nil @@ -91,7 +93,7 @@ // Stop implements HealthMonitor. func (hm *SequencerHealthMonitor) Stop() error { hm.log.Info("stopping health monitor") - close(hm.done) + hm.cancel() hm.wg.Wait()   hm.log.Info("health monitor stopped") @@ -103,7 +105,7 @@ func (hm *SequencerHealthMonitor) Subscribe() <-chan error { return hm.healthUpdateCh }   -func (hm *SequencerHealthMonitor) loop() { +func (hm *SequencerHealthMonitor) loop(ctx context.Context) { defer hm.wg.Done()   duration := time.Duration(hm.interval) * time.Second @@ -112,16 +114,16 @@ defer ticker.Stop()   for { select { - case <-hm.done: + case <-ctx.Done(): return case <-ticker.C: - err := hm.healthCheck() + err := hm.healthCheck(ctx) hm.metrics.RecordHealthCheck(err == nil, err) // Ensure that we exit cleanly if told to shutdown while still waiting to publish the health update select { case hm.healthUpdateCh <- err: continue - case <-hm.done: + case <-ctx.Done(): return } } @@ -133,8 +135,7 @@ // 1. unsafe head is progressing per block time // 2. unsafe head is not too far behind now (measured by unsafeInterval) // 3. safe head is progressing every configured batch submission interval // 4. peer count is above the configured minimum -func (hm *SequencerHealthMonitor) healthCheck() error { - ctx := context.Background() +func (hm *SequencerHealthMonitor) healthCheck(ctx context.Context) error { status, err := hm.node.SyncStatus(ctx) if err != nil { hm.log.Error("health monitor failed to get sync status", "err", err)
diff --git OP/op-conductor/health/monitor_test.go CELO/op-conductor/health/monitor_test.go index 1533e98a360cf4cbe9c063028e0e61ee21060168..1a38f7f3a79ef44870d4d9f118d9b757fe002d81 100644 --- OP/op-conductor/health/monitor_test.go +++ CELO/op-conductor/health/monitor_test.go @@ -6,6 +6,7 @@ "testing" "time"   "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite"   "github.com/ethereum-optimism/optimism/op-conductor/metrics" @@ -53,11 +54,10 @@ mockP2P = &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: healthyPeerCount, } - mockP2P.EXPECT().PeerStats(context.Background()).Return(ps1, nil) + mockP2P.EXPECT().PeerStats(mock.Anything).Return(ps1, nil) } monitor := &SequencerHealthMonitor{ log: s.log, - done: make(chan struct{}), interval: s.interval, metrics: &metrics.NoopMetricsImpl{}, healthUpdateCh: make(chan error), @@ -70,7 +70,7 @@ timeProviderFn: tp.Now, node: mockRollupClient, p2p: mockP2P, } - err := monitor.Start() + err := monitor.Start(context.Background()) s.NoError(err) return monitor } @@ -88,7 +88,7 @@ pc := &p2pMocks.API{} ps1 := &p2p.PeerStats{ Connected: unhealthyPeerCount, } - pc.EXPECT().PeerStats(context.Background()).Return(ps1, nil).Times(1) + pc.EXPECT().PeerStats(mock.Anything).Return(ps1, nil).Times(1)   monitor := s.SetupMonitor(now, 60, 60, rc, pc)
diff --git OP/op-conductor/rpc/api.go CELO/op-conductor/rpc/api.go index b5d954f5ceb0c614370d2455da72be43a3b9f466..2ed373b1bcad69cd2b90d30b2484645d5bc864d2 100644 --- OP/op-conductor/rpc/api.go +++ CELO/op-conductor/rpc/api.go @@ -15,6 +15,10 @@ var ErrNotLeader = errors.New("refusing to proxy request to non-leader sequencer")   // API defines the interface for the op-conductor API. type API interface { + // OverrideLeader is used to override the leader status, this is only used to return true for Leader() & LeaderWithID() calls. + // It does not impact the actual raft consensus leadership status. It is supposed to be used when the cluster is unhealthy + // and the node is the only one up, to allow batcher to be able to connect to the node, so that it could download blocks from the manually started sequencer. + OverrideLeader(ctx context.Context) error // Pause pauses op-conductor. Pause(ctx context.Context) error // Resume resumes op-conductor. @@ -32,17 +36,17 @@ Leader(ctx context.Context) (bool, error) // LeaderWithID returns the current leader's server info. LeaderWithID(ctx context.Context) (*consensus.ServerInfo, error) // AddServerAsVoter adds a server as a voter to the cluster. - AddServerAsVoter(ctx context.Context, id string, addr string) error + AddServerAsVoter(ctx context.Context, id string, addr string, version uint64) error // AddServerAsNonvoter adds a server as a non-voter to the cluster. non-voter will not participate in leader election. - AddServerAsNonvoter(ctx context.Context, id string, addr string) error + AddServerAsNonvoter(ctx context.Context, id string, addr string, version uint64) error // RemoveServer removes a server from the cluster. - RemoveServer(ctx context.Context, id string) error + RemoveServer(ctx context.Context, id string, version uint64) error // TransferLeader transfers leadership to another server. TransferLeader(ctx context.Context) error // TransferLeaderToServer transfers leadership to a specific server. TransferLeaderToServer(ctx context.Context, id string, addr string) error // ClusterMembership returns the current cluster membership configuration. - ClusterMembership(ctx context.Context) ([]*consensus.ServerInfo, error) + ClusterMembership(ctx context.Context) (*consensus.ClusterMembership, error)   // APIs called by op-node // Active returns true if op-conductor is active (not paused or stopped).
diff --git OP/op-conductor/rpc/backend.go CELO/op-conductor/rpc/backend.go index 96fab2c90d286b7cce3c19ce0b95bca69b64dd18..f42f09e5e9e1fee3859781b15d09ade2c96825e6 100644 --- OP/op-conductor/rpc/backend.go +++ CELO/op-conductor/rpc/backend.go @@ -2,6 +2,7 @@ package rpc   import ( "context" + "sync/atomic"   "github.com/ethereum/go-ethereum/log"   @@ -18,21 +19,21 @@ SequencerHealthy(ctx context.Context) bool   Leader(ctx context.Context) bool LeaderWithID(ctx context.Context) *consensus.ServerInfo - AddServerAsVoter(ctx context.Context, id string, addr string) error - AddServerAsNonvoter(ctx context.Context, id string, addr string) error - RemoveServer(ctx context.Context, id string) error + AddServerAsVoter(ctx context.Context, id string, addr string, version uint64) error + AddServerAsNonvoter(ctx context.Context, id string, addr string, version uint64) error + RemoveServer(ctx context.Context, id string, version uint64) error TransferLeader(ctx context.Context) error TransferLeaderToServer(ctx context.Context, id string, addr string) error CommitUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error - ClusterMembership(ctx context.Context) ([]*consensus.ServerInfo, error) + ClusterMembership(ctx context.Context) (*consensus.ClusterMembership, error) }   // APIBackend is the backend implementation of the API. // TODO: (https://github.com/ethereum-optimism/protocol-quest/issues/45) Add metrics tracer here. -// TODO: (https://github.com/ethereum-optimism/protocol-quest/issues/44) add tests after e2e setup. type APIBackend struct { - log log.Logger - con conductor + log log.Logger + con conductor + leaderOverride atomic.Bool }   // NewAPIBackend creates a new APIBackend instance. @@ -45,6 +46,12 @@ }   var _ API = (*APIBackend)(nil)   +// OverrideLeader implements API. +func (api *APIBackend) OverrideLeader(ctx context.Context) error { + api.leaderOverride.Store(true) + return nil +} + // Paused implements API. func (api *APIBackend) Paused(ctx context.Context) (bool, error) { return api.con.Paused(), nil @@ -61,13 +68,18 @@ return !api.con.Stopped() && !api.con.Paused(), nil }   // AddServerAsNonvoter implements API. -func (api *APIBackend) AddServerAsNonvoter(ctx context.Context, id string, addr string) error { - return api.con.AddServerAsNonvoter(ctx, id, addr) +func (api *APIBackend) AddServerAsNonvoter(ctx context.Context, id string, addr string, version uint64) error { + return api.con.AddServerAsNonvoter(ctx, id, addr, version) }   // AddServerAsVoter implements API. -func (api *APIBackend) AddServerAsVoter(ctx context.Context, id string, addr string) error { - return api.con.AddServerAsVoter(ctx, id, addr) +func (api *APIBackend) AddServerAsVoter(ctx context.Context, id string, addr string, version uint64) error { + return api.con.AddServerAsVoter(ctx, id, addr, version) +} + +// RemoveServer implements API. +func (api *APIBackend) RemoveServer(ctx context.Context, id string, version uint64) error { + return api.con.RemoveServer(ctx, id, version) }   // CommitUnsafePayload implements API. @@ -77,22 +89,25 @@ }   // Leader implements API, returns true if current conductor is leader of the cluster. func (api *APIBackend) Leader(ctx context.Context) (bool, error) { - return api.con.Leader(ctx), nil + return api.leaderOverride.Load() || api.con.Leader(ctx), nil }   // LeaderWithID implements API, returns the leader's server ID and address (not necessarily the current conductor). func (api *APIBackend) LeaderWithID(ctx context.Context) (*consensus.ServerInfo, error) { + if api.leaderOverride.Load() { + return &consensus.ServerInfo{ + ID: "N/A (Leader overridden)", + Addr: "N/A", + Suffrage: 0, + }, nil + } + return api.con.LeaderWithID(ctx), nil }   // Pause implements API. func (api *APIBackend) Pause(ctx context.Context) error { return api.con.Pause(ctx) -} - -// RemoveServer implements API. -func (api *APIBackend) RemoveServer(ctx context.Context, id string) error { - return api.con.RemoveServer(ctx, id) }   // Resume implements API. @@ -118,6 +133,6 @@ return api.con.SequencerHealthy(ctx), nil }   // ClusterMembership implements API. -func (api *APIBackend) ClusterMembership(ctx context.Context) ([]*consensus.ServerInfo, error) { +func (api *APIBackend) ClusterMembership(ctx context.Context) (*consensus.ClusterMembership, error) { return api.con.ClusterMembership(ctx) }
diff --git OP/op-conductor/rpc/client.go CELO/op-conductor/rpc/client.go index f8aac005cdd74c8b9d2efaabb4f9bdc09c786ba7..a625837e5e5fd41ddefe89c0215dac2c2f0312b1 100644 --- OP/op-conductor/rpc/client.go +++ CELO/op-conductor/rpc/client.go @@ -27,6 +27,11 @@ func prefixRPC(method string) string { return RPCNamespace + "_" + method }   +// OverrideLeader implements API. +func (c *APIClient) OverrideLeader(ctx context.Context) error { + return c.c.CallContext(ctx, nil, prefixRPC("overrideLeader")) +} + // Paused implements API. func (c *APIClient) Paused(ctx context.Context) (bool, error) { var paused bool @@ -49,13 +54,18 @@ return active, err }   // AddServerAsNonvoter implements API. -func (c *APIClient) AddServerAsNonvoter(ctx context.Context, id string, addr string) error { - return c.c.CallContext(ctx, nil, prefixRPC("addServerAsNonvoter"), id, addr) +func (c *APIClient) AddServerAsNonvoter(ctx context.Context, id string, addr string, version uint64) error { + return c.c.CallContext(ctx, nil, prefixRPC("addServerAsNonvoter"), id, addr, version) }   // AddServerAsVoter implements API. -func (c *APIClient) AddServerAsVoter(ctx context.Context, id string, addr string) error { - return c.c.CallContext(ctx, nil, prefixRPC("addServerAsVoter"), id, addr) +func (c *APIClient) AddServerAsVoter(ctx context.Context, id string, addr string, version uint64) error { + return c.c.CallContext(ctx, nil, prefixRPC("addServerAsVoter"), id, addr, version) +} + +// RemoveServer implements API. +func (c *APIClient) RemoveServer(ctx context.Context, id string, version uint64) error { + return c.c.CallContext(ctx, nil, prefixRPC("removeServer"), id, version) }   // Close closes the underlying RPC client. @@ -87,11 +97,6 @@ func (c *APIClient) Pause(ctx context.Context) error { return c.c.CallContext(ctx, nil, prefixRPC("pause")) }   -// RemoveServer implements API. -func (c *APIClient) RemoveServer(ctx context.Context, id string) error { - return c.c.CallContext(ctx, nil, prefixRPC("removeServer"), id) -} - // Resume implements API. func (c *APIClient) Resume(ctx context.Context) error { return c.c.CallContext(ctx, nil, prefixRPC("resume")) @@ -115,8 +120,8 @@ return healthy, err }   // ClusterMembership implements API. -func (c *APIClient) ClusterMembership(ctx context.Context) ([]*consensus.ServerInfo, error) { - var info []*consensus.ServerInfo - err := c.c.CallContext(ctx, &info, prefixRPC("clusterMembership")) - return info, err +func (c *APIClient) ClusterMembership(ctx context.Context) (*consensus.ClusterMembership, error) { + var clusterMembership consensus.ClusterMembership + err := c.c.CallContext(ctx, &clusterMembership, prefixRPC("clusterMembership")) + return &clusterMembership, err }
diff --git OP/op-dispute-mon/metrics/metrics.go CELO/op-dispute-mon/metrics/metrics.go index a119766c44e1538016a353f0b9eb0430ee1125c6..1fb196f157a47474fdd24f715c1d3fe0424dfa81 100644 --- OP/op-dispute-mon/metrics/metrics.go +++ CELO/op-dispute-mon/metrics/metrics.go @@ -163,6 +163,8 @@ RecordGameResolutionStatus(status ResolutionStatus, count int)   RecordCredit(expectation CreditExpectation, count int)   + RecordHonestWithdrawableAmounts(map[common.Address]*big.Int) + RecordClaims(statuses *ClaimStatuses)   RecordWithdrawalRequests(delayedWeth common.Address, matches bool, count int) @@ -208,7 +210,8 @@ info prometheus.GaugeVec up prometheus.Gauge   - credits prometheus.GaugeVec + credits prometheus.GaugeVec + honestWithdrawableAmounts prometheus.GaugeVec   lastOutputFetch prometheus.Gauge   @@ -294,6 +297,13 @@ Help: "Cumulative credits", }, []string{ "credit", "withdrawable", + }), + honestWithdrawableAmounts: *factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: Namespace, + Name: "honest_actor_pending_withdrawals", + Help: "Current amount of withdrawable ETH for an honest actor", + }, []string{ + "actor", }), claims: *factory.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, @@ -451,6 +461,12 @@ panic(fmt.Errorf("unknown credit expectation: %v", expectation)) } } m.credits.WithLabelValues(asLabels(expectation)...).Set(float64(count)) +} + +func (m *Metrics) RecordHonestWithdrawableAmounts(amounts map[common.Address]*big.Int) { + for addr, amount := range amounts { + m.honestWithdrawableAmounts.WithLabelValues(addr.Hex()).Set(weiToEther(amount)) + } }   func (m *Metrics) RecordClaims(statuses *ClaimStatuses) {
diff --git OP/op-dispute-mon/metrics/noop.go CELO/op-dispute-mon/metrics/noop.go index 7fe2f15b817affbe6d07b8ead0e13a8bc0b4d692..a136e99457aac34860bc6a614c6b7c8fe436b9e5 100644 --- OP/op-dispute-mon/metrics/noop.go +++ CELO/op-dispute-mon/metrics/noop.go @@ -28,6 +28,8 @@ func (*NoopMetricsImpl) RecordGameResolutionStatus(_ ResolutionStatus, _ int) {}   func (*NoopMetricsImpl) RecordCredit(_ CreditExpectation, _ int) {}   +func (*NoopMetricsImpl) RecordHonestWithdrawableAmounts(map[common.Address]*big.Int) {} + func (*NoopMetricsImpl) RecordClaims(_ *ClaimStatuses) {}   func (*NoopMetricsImpl) RecordWithdrawalRequests(_ common.Address, _ bool, _ int) {}
diff --git OP/op-dispute-mon/mon/bonds/monitor.go CELO/op-dispute-mon/mon/bonds/monitor.go index 2ce23dc472e749ffe64b78ea1540ea4ac2154009..efa7804d340850125981f0699193c85f30df9e0b 100644 --- OP/op-dispute-mon/mon/bonds/monitor.go +++ CELO/op-dispute-mon/mon/bonds/monitor.go @@ -17,19 +17,22 @@ type BondMetrics interface { RecordCredit(expectation metrics.CreditExpectation, count int) RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int) + RecordHonestWithdrawableAmounts(map[common.Address]*big.Int) }   type Bonds struct { - logger log.Logger - clock RClock - metrics BondMetrics + logger log.Logger + clock RClock + metrics BondMetrics + honestActors types.HonestActors }   -func NewBonds(logger log.Logger, metrics BondMetrics, clock RClock) *Bonds { +func NewBonds(logger log.Logger, metrics BondMetrics, honestActors types.HonestActors, clock RClock) *Bonds { return &Bonds{ - logger: logger, - clock: clock, - metrics: metrics, + logger: logger, + clock: clock, + metrics: metrics, + honestActors: honestActors, } }   @@ -47,6 +50,10 @@ }   func (b *Bonds) checkCredits(games []*types.EnrichedGameData) { creditMetrics := make(map[metrics.CreditExpectation]int) + honestWithdrawableAmounts := make(map[common.Address]*big.Int) + for address := range b.honestActors { + honestWithdrawableAmounts[address] = big.NewInt(0) + }   for _, game := range games { // Check if the max duration has been reached for this game @@ -94,6 +101,12 @@ expected = big.NewInt(0) } comparison := actual.Cmp(expected) if maxDurationReached { + if actual.Cmp(big.NewInt(0)) > 0 && b.honestActors.Contains(recipient) { + total := honestWithdrawableAmounts[recipient] + total = new(big.Int).Add(total, actual) + honestWithdrawableAmounts[recipient] = total + b.logger.Warn("Found unclaimed credit", "recipient", recipient, "game", game.Proxy, "amount", actual) + } if comparison > 0 { creditMetrics[metrics.CreditAboveWithdrawable] += 1 b.logger.Warn("Credit above expected amount", "recipient", recipient, "expected", expected, "actual", actual, "game", game.Proxy, "withdrawable", "withdrawable") @@ -123,4 +136,5 @@ b.metrics.RecordCredit(metrics.CreditBelowNonWithdrawable, creditMetrics[metrics.CreditBelowNonWithdrawable]) b.metrics.RecordCredit(metrics.CreditEqualNonWithdrawable, creditMetrics[metrics.CreditEqualNonWithdrawable]) b.metrics.RecordCredit(metrics.CreditAboveNonWithdrawable, creditMetrics[metrics.CreditAboveNonWithdrawable]) + b.metrics.RecordHonestWithdrawableAmounts(honestWithdrawableAmounts) }
diff --git OP/op-dispute-mon/mon/bonds/monitor_test.go CELO/op-dispute-mon/mon/bonds/monitor_test.go index 311dc6ce4d96fcefe1bb41b8d8be3e547d828a7a..5fd766b870613b7f6537e496be0f2c651d1eca10 100644 --- OP/op-dispute-mon/mon/bonds/monitor_test.go +++ CELO/op-dispute-mon/mon/bonds/monitor_test.go @@ -17,7 +17,10 @@ "github.com/stretchr/testify/require" )   var ( - frozen = time.Unix(int64(time.Hour.Seconds()), 0) + frozen = time.Unix(int64(time.Hour.Seconds()), 0) + honestActor1 = common.Address{0x11, 0xaa} + honestActor2 = common.Address{0x22, 0xbb} + honestActor3 = common.Address{0x33, 0xcc} )   func TestCheckBonds(t *testing.T) { @@ -61,8 +64,8 @@ require.Nil(t, logs.FindLog(testlog.NewAttributesFilter("delayedWETH", weth1.Hex()))) }   func TestCheckRecipientCredit(t *testing.T) { - addr1 := common.Address{0x1a} - addr2 := common.Address{0x2b} + addr1 := honestActor1 + addr2 := honestActor2 addr3 := common.Address{0x3c} addr4 := common.Address{0x4d} notRootPosition := types.NewPositionFromGIndex(big.NewInt(2)) @@ -273,7 +276,7 @@ game4 := &monTypes.EnrichedGameData{ MaxClockDuration: 10, WETHDelay: 10 * time.Second, GameMetadata: gameTypes.GameMetadata{ - Proxy: common.Address{44}, + Proxy: common.Address{0x44}, Timestamp: uint64(frozen.Unix()) - 22, }, BlockNumberChallenged: true, @@ -346,6 +349,14 @@ require.Equal(t, 3, m.credits[metrics.CreditBelowNonWithdrawable], "CreditBelowNonWithdrawable") require.Equal(t, 2, m.credits[metrics.CreditEqualNonWithdrawable], "CreditEqualNonWithdrawable") require.Equal(t, 2, m.credits[metrics.CreditAboveNonWithdrawable], "CreditAboveNonWithdrawable")   + require.Len(t, m.honestWithdrawable, 3) + requireBigInt := func(name string, expected, actual *big.Int) { + require.Truef(t, expected.Cmp(actual) == 0, "Expected %v withdrawable to be %v but was %v", name, expected, actual) + } + requireBigInt("honest addr1", m.honestWithdrawable[addr1], big.NewInt(19)) + requireBigInt("honest addr2", m.honestWithdrawable[addr2], big.NewInt(13)) + requireBigInt("honest addr3", m.honestWithdrawable[honestActor3], big.NewInt(0)) + // Logs from game1 // addr1 is correct so has no logs // addr2 is below expected before max duration, so warn about early withdrawal @@ -371,8 +382,18 @@ testlog.NewAttributesFilter("recipient", addr4.Hex()), testlog.NewAttributesFilter("withdrawable", "non_withdrawable")))   // Logs from game 2 - // addr1 is below expected - no warning as withdrawals may now be possible - // addr2 is correct + // addr1 is below expected - no warning as withdrawals may now be possible, but has unclaimed credit + require.NotNil(t, logs.FindLog( + testlog.NewLevelFilter(log.LevelWarn), + testlog.NewMessageFilter("Found unclaimed credit"), + testlog.NewAttributesFilter("game", game2.Proxy.Hex()), + testlog.NewAttributesFilter("recipient", addr1.Hex()))) + // addr2 is correct but has unclaimed credit + require.NotNil(t, logs.FindLog( + testlog.NewLevelFilter(log.LevelWarn), + testlog.NewMessageFilter("Found unclaimed credit"), + testlog.NewAttributesFilter("game", game2.Proxy.Hex()), + testlog.NewAttributesFilter("recipient", addr2.Hex()))) // addr3 is above expected - warn require.NotNil(t, logs.FindLog( testlog.NewLevelFilter(log.LevelWarn), @@ -401,8 +422,18 @@ testlog.NewAttributesFilter("recipient", addr4.Hex()), testlog.NewAttributesFilter("withdrawable", "non_withdrawable")))   // Logs from game 4 - // addr1 is correct so has no logs - // addr2 is below expected before max duration, no long because withdrawals may be possible + // addr1 is correct but has unclaimed credit + require.NotNil(t, logs.FindLog( + testlog.NewLevelFilter(log.LevelWarn), + testlog.NewMessageFilter("Found unclaimed credit"), + testlog.NewAttributesFilter("game", game4.Proxy.Hex()), + testlog.NewAttributesFilter("recipient", addr1.Hex()))) + // addr2 is below expected before max duration, no log because withdrawals may be possible but warn about unclaimed + require.NotNil(t, logs.FindLog( + testlog.NewLevelFilter(log.LevelWarn), + testlog.NewMessageFilter("Found unclaimed credit"), + testlog.NewAttributesFilter("game", game4.Proxy.Hex()), + testlog.NewAttributesFilter("recipient", addr2.Hex()))) // addr3 is not involved so no logs // addr4 is above expected before max duration, so warn require.NotNil(t, logs.FindLog( @@ -419,13 +450,19 @@ metrics := &stubBondMetrics{ credits: make(map[metrics.CreditExpectation]int), recorded: make(map[common.Address]Collateral), } - bonds := NewBonds(logger, metrics, clock.NewDeterministicClock(frozen)) + honestActors := monTypes.NewHonestActors([]common.Address{honestActor1, honestActor2, honestActor3}) + bonds := NewBonds(logger, metrics, honestActors, clock.NewDeterministicClock(frozen)) return bonds, metrics, logs }   type stubBondMetrics struct { - credits map[metrics.CreditExpectation]int - recorded map[common.Address]Collateral + credits map[metrics.CreditExpectation]int + recorded map[common.Address]Collateral + honestWithdrawable map[common.Address]*big.Int +} + +func (s *stubBondMetrics) RecordHonestWithdrawableAmounts(values map[common.Address]*big.Int) { + s.honestWithdrawable = values }   func (s *stubBondMetrics) RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int) {
diff --git OP/op-dispute-mon/mon/claims.go CELO/op-dispute-mon/mon/claims.go index 2789ef4e6dc9347018fdde811c41183d63bfd59f..4a5d99b6112722bc0db01b9b703f197a6b2643a1 100644 --- OP/op-dispute-mon/mon/claims.go +++ CELO/op-dispute-mon/mon/claims.go @@ -25,16 +25,12 @@ type ClaimMonitor struct { logger log.Logger clock RClock - honestActors map[common.Address]bool // Map for efficient lookup + honestActors types.HonestActors metrics ClaimMetrics }   -func NewClaimMonitor(logger log.Logger, clock RClock, honestActors []common.Address, metrics ClaimMetrics) *ClaimMonitor { - actors := make(map[common.Address]bool) - for _, actor := range honestActors { - actors[actor] = true - } - return &ClaimMonitor{logger, clock, actors, metrics} +func NewClaimMonitor(logger log.Logger, clock RClock, honestActors types.HonestActors, metrics ClaimMetrics) *ClaimMonitor { + return &ClaimMonitor{logger, clock, honestActors, metrics} }   func (c *ClaimMonitor) CheckClaims(games []*types.EnrichedGameData) {
diff --git OP/op-dispute-mon/mon/claims_test.go CELO/op-dispute-mon/mon/claims_test.go index d5e9578e4f058ea769607c0d7e9e498ee13c1bf3..3491eb1a779e87d835d6ea474f7e2ced25727267 100644 --- OP/op-dispute-mon/mon/claims_test.go +++ CELO/op-dispute-mon/mon/claims_test.go @@ -194,10 +194,10 @@ func newTestClaimMonitor(t *testing.T) (*ClaimMonitor, *clock.DeterministicClock, *stubClaimMetrics, *testlog.CapturingHandler) { logger, handler := testlog.CaptureLogger(t, log.LvlInfo) cl := clock.NewDeterministicClock(frozen) metrics := &stubClaimMetrics{} - honestActors := []common.Address{ + honestActors := types.NewHonestActors([]common.Address{ {0x01}, {0x02}, - } + }) monitor := NewClaimMonitor(logger, cl, honestActors, metrics) return monitor, cl, metrics, handler }
diff --git OP/op-dispute-mon/mon/service.go CELO/op-dispute-mon/mon/service.go index f8df6132f095bcdcf8bbd91ff946c3ed12c6a8dd..e637b23d63b0d79298fe0bd806868e8eae0e26a7 100644 --- OP/op-dispute-mon/mon/service.go +++ CELO/op-dispute-mon/mon/service.go @@ -8,6 +8,7 @@ "math/big" "sync/atomic"   "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/bonds" + "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" @@ -28,9 +29,10 @@ "github.com/ethereum-optimism/optimism/op-service/sources/batching" )   type Service struct { - logger log.Logger - metrics metrics.Metricer - monitor *gameMonitor + logger log.Logger + metrics metrics.Metricer + monitor *gameMonitor + honestActors types.HonestActors   factoryContract *contracts.DisputeGameFactoryContract   @@ -56,9 +58,10 @@ // NewService creates a new Service. func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Service, error) { s := &Service{ - cl: clock.SystemClock, - logger: logger, - metrics: metrics.NewMetrics(), + cl: clock.SystemClock, + logger: logger, + metrics: metrics.NewMetrics(), + honestActors: types.NewHonestActors(cfg.HonestActors), }   if err := s.initFromConfig(ctx, cfg); err != nil { @@ -105,7 +108,7 @@ return nil }   func (s *Service) initClaimMonitor(cfg *config.Config) { - s.claims = NewClaimMonitor(s.logger, s.cl, cfg.HonestActors, s.metrics) + s.claims = NewClaimMonitor(s.logger, s.cl, s.honestActors, s.metrics) }   func (s *Service) initResolutionMonitor() { @@ -142,7 +145,7 @@ s.forecast = NewForecast(s.logger, s.metrics) }   func (s *Service) initBonds() { - s.bonds = bonds.NewBonds(s.logger, s.metrics, s.cl) + s.bonds = bonds.NewBonds(s.logger, s.metrics, s.honestActors, s.cl) }   func (s *Service) initOutputRollupClient(ctx context.Context, cfg *config.Config) error {
diff --git OP/op-dispute-mon/mon/types/honest_actors.go CELO/op-dispute-mon/mon/types/honest_actors.go new file mode 100644 index 0000000000000000000000000000000000000000..31398dfcbd821c3236966493449ad6adafa0bef3 --- /dev/null +++ CELO/op-dispute-mon/mon/types/honest_actors.go @@ -0,0 +1,17 @@ +package types + +import "github.com/ethereum/go-ethereum/common" + +type HonestActors map[common.Address]bool // Map for efficient lookup + +func NewHonestActors(honestActors []common.Address) HonestActors { + actors := make(map[common.Address]bool) + for _, actor := range honestActors { + actors[actor] = true + } + return actors +} + +func (h HonestActors) Contains(addr common.Address) bool { + return h[addr] +}
diff --git OP/op-plasma/cli.go CELO/op-plasma/cli.go index 2664a19c7996b9c97f4dd2b51b5b568afbb17a64..f5c4f123f49bf762e5188dd155caaa2da3a53359 100644 --- OP/op-plasma/cli.go +++ CELO/op-plasma/cli.go @@ -7,44 +7,56 @@ "github.com/urfave/cli/v2" )   -const ( - EnabledFlagName = "plasma.enabled" - DaServerAddressFlagName = "plasma.da-server" - VerifyOnReadFlagName = "plasma.verify-on-read" - DaServiceFlag = "plasma.da-service" +var ( + EnabledFlagName, EnabledFlagAlias = altDAFlags("enabled") + DaServerAddressFlagName, DaServerAddressFlagAlias = altDAFlags("da-server") + VerifyOnReadFlagName, VerifyOnReadFlagAlias = altDAFlags("verify-on-read") + DaServiceFlag, DaServiceFlagAlias = altDAFlags("da-service") )   -func plasmaEnv(envprefix, v string) []string { - return []string{envprefix + "_PLASMA_" + v} +// altDAFlags returns the flag names for altDA, with an Alias for plasma +func altDAFlags(v string) (string, string) { + return "altda." + v, + "plasma." + v +} + +func altDAEnvs(envprefix, v string) []string { + return []string{ + envprefix + "_ALTDA_" + v, + envprefix + "_PLASMA_" + v} }   func CLIFlags(envPrefix string, category string) []cli.Flag { return []cli.Flag{ &cli.BoolFlag{ Name: EnabledFlagName, - Usage: "Enable plasma mode\nPlasma Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues.", + Aliases: []string{EnabledFlagAlias}, + Usage: "Enable Alt-DA mode\nAlt-DA Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues.", Value: false, - EnvVars: plasmaEnv(envPrefix, "ENABLED"), + EnvVars: altDAEnvs(envPrefix, "ENABLED"), Category: category, }, &cli.StringFlag{ Name: DaServerAddressFlagName, + Aliases: []string{DaServerAddressFlagAlias}, Usage: "HTTP address of a DA Server", - EnvVars: plasmaEnv(envPrefix, "DA_SERVER"), + EnvVars: altDAEnvs(envPrefix, "DA_SERVER"), Category: category, }, &cli.BoolFlag{ Name: VerifyOnReadFlagName, + Aliases: []string{VerifyOnReadFlagAlias}, Usage: "Verify input data matches the commitments from the DA storage service", Value: true, - EnvVars: plasmaEnv(envPrefix, "VERIFY_ON_READ"), + EnvVars: altDAEnvs(envPrefix, "VERIFY_ON_READ"), Category: category, }, &cli.BoolFlag{ Name: DaServiceFlag, - Usage: "Use DA service type where commitments are generated by plasma server", + Aliases: []string{DaServiceFlagAlias}, + Usage: "Use DA service type where commitments are generated by Alt-DA server", Value: false, - EnvVars: plasmaEnv(envPrefix, "DA_SERVICE"), + EnvVars: altDAEnvs(envPrefix, "DA_SERVICE"), Category: category, }, }
diff --git OP/op-plasma/commitment.go CELO/op-plasma/commitment.go index 0edb4ad2f14c16dcfc1e414a26aa6a25741e2fb7..763d2b8cf20cbb1bb2dbf78a8d00f981f161f26e 100644 --- OP/op-plasma/commitment.go +++ CELO/op-plasma/commitment.go @@ -2,6 +2,7 @@ package plasma   import ( "bytes" + "encoding/hex" "errors" "fmt"   @@ -29,7 +30,7 @@ } }   // CommitmentType describes the binary format of the commitment. -// KeccakCommitmentStringType is the default commitment type for the centralized DA storage. +// KeccakCommitmentType is the default commitment type for the centralized DA storage. // GenericCommitmentType indicates an opaque bytestring that the op-node never opens. const ( Keccak256CommitmentType CommitmentType = 0 @@ -44,6 +45,7 @@ CommitmentType() CommitmentType Encode() []byte TxData() []byte Verify(input []byte) error + String() string }   // Keccak256Commitment is an implementation of CommitmentData that uses Keccak256 as the commitment function. @@ -124,6 +126,10 @@ } return nil }   +func (c Keccak256Commitment) String() string { + return hex.EncodeToString(c.Encode()) +} + // NewGenericCommitment creates a new commitment from the given input. func NewGenericCommitment(input []byte) GenericCommitment { return GenericCommitment(input) @@ -156,3 +162,7 @@ // Verify always returns true for GenericCommitment because the DA Server must validate the data before returning it to the op-node. func (c GenericCommitment) Verify(input []byte) error { return nil } + +func (c GenericCommitment) String() string { + return hex.EncodeToString(c.Encode()) +}
diff --git OP/op-plasma/daclient_test.go CELO/op-plasma/daclient_test.go index 8f5aa55053bcd64ac5ead40bc3ab46db795c7ba1..c933526900c5654247397f0ee0991a109f955304 100644 --- OP/op-plasma/daclient_test.go +++ CELO/op-plasma/daclient_test.go @@ -9,7 +9,6 @@ "testing"   "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -116,7 +115,7 @@ cfg := CLIConfig{ Enabled: true, DAServerURL: fmt.Sprintf("http://%s", server.Endpoint()), VerifyOnRead: false, - GenericDA: true, + GenericDA: false, } require.NoError(t, cfg.Check())   @@ -129,7 +128,7 @@ comm, err := client.SetInput(ctx, input) require.NoError(t, err)   - require.Equal(t, comm, NewGenericCommitment(crypto.Keccak256(input))) + require.Equal(t, comm.String(), NewKeccak256Commitment(input).String())   stored, err := client.GetInput(ctx, comm) require.NoError(t, err) @@ -144,7 +143,7 @@ _, err = client.GetInput(ctx, comm) require.NoError(t, err)   // test not found error - comm = NewGenericCommitment(RandomData(rng, 32)) + comm = NewKeccak256Commitment(RandomData(rng, 32)) _, err = client.GetInput(ctx, comm) require.ErrorIs(t, err, ErrNotFound)   @@ -157,6 +156,6 @@ require.NoError(t, server.Stop()) _, err = client.SetInput(ctx, input) require.Error(t, err)   - _, err = client.GetInput(ctx, NewGenericCommitment(input)) + _, err = client.GetInput(ctx, NewKeccak256Commitment(input)) require.Error(t, err) }
diff --git OP/op-plasma/damgr.go CELO/op-plasma/damgr.go index b3604ac1c7ef9d981dcfddb6f6416e5738e4253e..90e18cecb4c29720a41e06e77df76d880ec1ffc4 100644 --- OP/op-plasma/damgr.go +++ CELO/op-plasma/damgr.go @@ -63,22 +63,18 @@ type DA struct { log log.Logger cfg Config metrics Metricer - storage DAStorage + state *State // the DA state keeps track of all the commitments and their challenge status.   - // the DA state keeps track of all the commitments and their challenge status. - state *State + challengeOrigin eth.BlockID // the highest l1 block we synced challenge contract events from + commitmentOrigin eth.BlockID // the highest l1 block we read commitments from + finalizedHead eth.L1BlockRef // the latest recorded finalized head as per the challenge contract + l1FinalizedHead eth.L1BlockRef // the latest recorded finalized head as per the l1 finalization signal   - // the latest l1 block we synced challenge contract events from - origin eth.BlockID - // the latest recorded finalized head as per the challenge contract - finalizedHead eth.L1BlockRef - // the latest recorded finalized head as per the l1 finalization signal - l1FinalizedHead eth.L1BlockRef // flag the reset function we are resetting because of an expired challenge resetting bool   - finalizedHeadSignalFunc HeadSignalFn + finalizedHeadSignalHandler HeadSignalFn }   // NewPlasmaDA creates a new PlasmaDA instance with the given log and CLIConfig. @@ -93,7 +89,7 @@ log: log, cfg: cfg, storage: storage, metrics: metrics, - state: NewState(log, metrics), + state: NewState(log, metrics, cfg), } }   @@ -112,40 +108,64 @@ // OnFinalizedHeadSignal sets the callback function to be called when the finalized head is updated. // This will signal to the engine queue that will set the proper L2 block as finalized. func (d *DA) OnFinalizedHeadSignal(f HeadSignalFn) { - d.finalizedHeadSignalFunc = f + d.finalizedHeadSignalHandler = f }   -// Finalize takes the L1 finality signal, compares the plasma finalized block and forwards the finality -// signal to the engine queue based on whichever is most behind. -func (d *DA) Finalize(l1Finalized eth.L1BlockRef) { - ref := d.finalizedHead - d.log.Info("received l1 finalized signal, forwarding to engine queue", "l1", l1Finalized, "plasma", ref) - // if the l1 finalized head is behind it is the finalized head - if l1Finalized.Number < d.finalizedHead.Number { - ref = l1Finalized +// updateFinalizedHead sets the finalized head and prunes the state to the L1 Finalized head. +// the finalized head is set to the latest reference pruned in this way. +// It is called by the Finalize function, as it has an L1 finalized head to use. +func (d *DA) updateFinalizedHead(l1Finalized eth.L1BlockRef) { + d.l1FinalizedHead = l1Finalized + // Prune the state to the finalized head + d.state.Prune(l1Finalized.ID()) + d.finalizedHead = d.state.lastPrunedCommitment +} + +// updateFinalizedFromL1 updates the finalized head based on the challenge window. +// it uses the L1 fetcher to get the block reference at the finalized head - challenge window. +// It is called in AdvanceL1Origin if there are no commitments to finalize, as it has an L1 fetcher to use. +func (d *DA) updateFinalizedFromL1(ctx context.Context, l1 L1Fetcher) error { + // don't update if the finalized head is smaller than the challenge window + if d.l1FinalizedHead.Number < d.cfg.ChallengeWindow { + return nil } - // prune finalized state - d.state.Prune(ref.Number) + ref, err := l1.L1BlockRefByNumber(ctx, d.l1FinalizedHead.Number-d.cfg.ChallengeWindow) + if err != nil { + return err + } + d.finalizedHead = ref + return nil +}   - if d.finalizedHeadSignalFunc == nil { - d.log.Warn("finalized head signal function not set") +// Finalize sets the L1 finalized head signal and calls the handler function if set. +func (d *DA) Finalize(l1Finalized eth.L1BlockRef) { + d.updateFinalizedHead(l1Finalized) + d.metrics.RecordChallengesHead("finalized", d.finalizedHead.Number) + + // Record and Log the latest L1 finalized head + d.log.Info("received l1 finalized signal, forwarding plasma finalization to finalizedHeadSignalHandler", + "l1", l1Finalized, + "plasma", d.finalizedHead) + + // execute the handler function if set + // the handler function is called with the plasma finalized head + if d.finalizedHeadSignalHandler == nil { + d.log.Warn("finalized head signal handler not set") return } - - // signal the engine queue - d.finalizedHeadSignalFunc(ref) + d.finalizedHeadSignalHandler(d.finalizedHead) }   // LookAhead increments the challenges origin and process the new block if it exists. // It is used when the derivation pipeline stalls due to missing data and we need to continue // syncing challenge events until the challenge is resolved or expires. func (d *DA) LookAhead(ctx context.Context, l1 L1Fetcher) error { - blkRef, err := l1.L1BlockRefByNumber(ctx, d.origin.Number+1) + blkRef, err := l1.L1BlockRefByNumber(ctx, d.challengeOrigin.Number+1) // temporary error, will do a backoff if err != nil { return err } - return d.AdvanceL1Origin(ctx, l1, blkRef.ID()) + return d.AdvanceChallengeOrigin(ctx, l1, blkRef.ID()) }   // Reset the challenge event derivation origin in case of L1 reorg @@ -157,9 +177,12 @@ // call any further stage to step. Thus the state will NOT be cleared if the reset originates // from this stage of the pipeline. if d.resetting { d.resetting = false + d.commitmentOrigin = base.ID() + d.state.ClearCommitments() } else { // resetting due to L1 reorg, clear state - d.origin = base.ID() + d.challengeOrigin = base.ID() + d.commitmentOrigin = base.ID() d.state.Reset() } return io.EOF @@ -167,22 +190,26 @@ }   // GetInput returns the input data for the given commitment bytes. blockNumber is required to lookup // the challenge status in the DataAvailabilityChallenge L1 contract. -func (d *DA) GetInput(ctx context.Context, l1 L1Fetcher, comm CommitmentData, blockId eth.BlockID) (eth.Data, error) { +func (d *DA) GetInput(ctx context.Context, l1 L1Fetcher, comm CommitmentData, blockId eth.L1BlockRef) (eth.Data, error) { // If it's not the right commitment type, report it as an expired commitment in order to skip it if d.cfg.CommitmentType != comm.CommitmentType() { return nil, fmt.Errorf("invalid commitment type; expected: %v, got: %v: %w", d.cfg.CommitmentType, comm.CommitmentType(), ErrExpiredChallenge) } - // If the challenge head is ahead in the case of a pipeline reset or stall, we might have synced a - // challenge event for this commitment. Otherwise we mark the commitment as part of the canonical - // chain so potential future challenge events can be selected. - ch := d.state.GetOrTrackChallenge(comm.Encode(), blockId.Number, d.cfg.ChallengeWindow) + status := d.state.GetChallengeStatus(comm, blockId.Number) + // check if the challenge is expired + if status == ChallengeExpired { + // Don't track the expired commitment. If we hit this case we have seen an expired challenge, but never used the data. + // this indicates that the data which might cause us to reorg is expired (not to be used) so we can optimize by skipping the reorg. + // If we used the data & then expire the challenge later, we do that during the AdvanceChallengeOrigin step + return nil, ErrExpiredChallenge + } + // Record the commitment for later finalization / invalidation + d.state.TrackCommitment(comm, blockId) + d.log.Info("getting input", "comm", comm, "status", status)   // Fetch the input from the DA storage. data, err := d.storage.GetInput(ctx, comm) - - // data is not found in storage but may be available if the challenge was resolved. notFound := errors.Is(ErrNotFound, err) - if err != nil && !notFound { d.log.Error("failed to get preimage", "err", err) // the storage client request failed for some other reason @@ -190,103 +217,117 @@ // in which case derivation pipeline should be retried return nil, err }   - switch ch.challengeStatus { - case ChallengeActive: - if d.isExpired(ch.expiresAt) { - // this challenge has expired, this input must be skipped - return nil, ErrExpiredChallenge - } else if notFound { - // data is missing and a challenge is active, we must wait for the challenge to resolve + // If the data is not found, things are handled differently based on the challenge status. + if notFound { + log.Warn("data not found for the given commitment", "comm", comm, "status", status, "block", blockId.Number) + switch status { + case ChallengeUninitialized: + // If this commitment was never challenged & we can't find the data, treat it as unrecoverable. + if d.challengeOrigin.Number > blockId.Number+d.cfg.ChallengeWindow { + return nil, ErrMissingPastWindow + } + // Otherwise continue syncing challenges hoping it eventually is challenged and resolved + if err := d.LookAhead(ctx, l1); err != nil { + return nil, err + } + return nil, ErrPendingChallenge + case ChallengeActive: + // If the commitment is active, we must wait for the challenge to resolve // hence we continue syncing new origins to sync the new challenge events. + // Active challenges are expired by the AdvanceChallengeOrigin function which calls state.ExpireChallenges if err := d.LookAhead(ctx, l1); err != nil { return nil, err } return nil, ErrPendingChallenge - } - case ChallengeExpired: - // challenge was marked as expired, skip - return nil, ErrExpiredChallenge - case ChallengeResolved: - // challenge was resolved, data is available in storage, return directly - if !notFound { - return data, nil - } - // Generic Commitments don't resolve from L1 so if we still can't find the data with out of luck - if comm.CommitmentType() == GenericCommitmentType { - return nil, ErrMissingPastWindow - } - // data not found in storage, return from challenge resolved input - resolvedInput, err := d.state.GetResolvedInput(comm.Encode()) - if err != nil { - return nil, err - } - return resolvedInput, nil - default: - if notFound { - if d.isExpired(ch.expiresAt) { - // we're past the challenge window and the data is not available + case ChallengeResolved: + // Generic Commitments don't resolve from L1 so if we still can't find the data we're out of luck + if comm.CommitmentType() == GenericCommitmentType { return nil, ErrMissingPastWindow - } else { - // continue syncing challenges hoping it eventually is challenged and resolved - if err := d.LookAhead(ctx, l1); err != nil { - return nil, err - } - return nil, ErrPendingChallenge + } + // Keccak commitments resolve from L1, so we should have the data in the challenge resolved input + if comm.CommitmentType() == Keccak256CommitmentType { + ch, _ := d.state.GetChallenge(comm, blockId.Number) + return ch.input, nil } } } + // regardless of the potential notFound error, if this challenge status is not handled, return an error + if status != ChallengeUninitialized && status != ChallengeActive && status != ChallengeResolved { + return nil, fmt.Errorf("unknown challenge status: %v", status) + }   return data, nil }   -// isExpired returns whether the given expiration block number is lower or equal to the current head -func (d *DA) isExpired(bn uint64) bool { - return d.origin.Number >= bn +// AdvanceChallengeOrigin reads & stores challenge events for the given L1 block +func (d *DA) AdvanceChallengeOrigin(ctx context.Context, l1 L1Fetcher, block eth.BlockID) error { + // do not repeat for the same or old origin + if block.Number <= d.challengeOrigin.Number { + return nil + } + + // load challenge events from the l1 block + if err := d.loadChallengeEvents(ctx, l1, block); err != nil { + return err + } + + // Expire challenges + d.state.ExpireChallenges(block) + + // set and record the new challenge origin + d.challengeOrigin = block + d.metrics.RecordChallengesHead("latest", d.challengeOrigin.Number) + d.log.Info("processed plasma challenge origin", "origin", block) + return nil }   -// AdvanceL1Origin syncs any challenge events included in the l1 block, expires any active challenges -// after the new resolveWindow, computes and signals the new finalized head and sets the l1 block -// as the new head for tracking challenges. If forwards an error if any new challenge have expired to -// trigger a derivation reset. -func (d *DA) AdvanceL1Origin(ctx context.Context, l1 L1Fetcher, block eth.BlockID) error { +// AdvanceCommitmentOrigin updates the commitment origin and the finalized head. +func (d *DA) AdvanceCommitmentOrigin(ctx context.Context, l1 L1Fetcher, block eth.BlockID) error { // do not repeat for the same origin - if block.Number <= d.origin.Number { + if block.Number <= d.commitmentOrigin.Number { return nil } - // sync challenges for the given block ID - if err := d.LoadChallengeEvents(ctx, l1, block); err != nil { - return err - } - // advance challenge window, computing the finalized head - bn, err := d.state.ExpireChallenges(block.Number) + + // Expire commitments + err := d.state.ExpireCommitments(block) if err != nil { // warn the reset function not to clear the state d.resetting = true return err }   - // finalized head signal is called only when the finalized head number increases - // and the l1 finalized head ahead of the DA finalized head. - if bn > d.finalizedHead.Number { - ref, err := l1.L1BlockRefByNumber(ctx, bn) - if err != nil { + // set and record the new commitment origin + d.commitmentOrigin = block + d.metrics.RecordChallengesHead("latest", d.challengeOrigin.Number) + d.log.Info("processed plasma l1 origin", "origin", block, "finalized", d.finalizedHead.ID(), "l1-finalize", d.l1FinalizedHead.ID()) + + return nil +} + +// AdvanceL1Origin syncs any challenge events included in the l1 block, expires any active challenges +// after the new resolveWindow, computes and signals the new finalized head and sets the l1 block +// as the new head for tracking challenges and commitments. If forwards an error if any new challenge have expired to +// trigger a derivation reset. +func (d *DA) AdvanceL1Origin(ctx context.Context, l1 L1Fetcher, block eth.BlockID) error { + if err := d.AdvanceChallengeOrigin(ctx, l1, block); err != nil { + return fmt.Errorf("failed to advance challenge origin: %w", err) + } + if err := d.AdvanceCommitmentOrigin(ctx, l1, block); err != nil { + return fmt.Errorf("failed to advance commitment origin: %w", err) + } + // if there are no commitments, we can calculate the finalized head based on the challenge window + // otherwise, the finalization signal is used to set the finalized head + if d.state.NoCommitments() { + if err := d.updateFinalizedFromL1(ctx, l1); err != nil { return err } - d.metrics.RecordChallengesHead("finalized", bn) - - // keep track of finalized had so it can be picked up by the - // l1 finalization signal - d.finalizedHead = ref + d.metrics.RecordChallengesHead("finalized", d.finalizedHead.Number) } - d.origin = block - d.metrics.RecordChallengesHead("latest", d.origin.Number) - - d.log.Info("processed plasma l1 origin", "origin", block, "next-finalized", bn, "finalized", d.finalizedHead.Number, "l1-finalize", d.l1FinalizedHead.Number) return nil }   -// LoadChallengeEvents fetches the l1 block receipts and updates the challenge status -func (d *DA) LoadChallengeEvents(ctx context.Context, l1 L1Fetcher, block eth.BlockID) error { +// loadChallengeEvents fetches the l1 block receipts and updates the challenge status +func (d *DA) loadChallengeEvents(ctx context.Context, l1 L1Fetcher, block eth.BlockID) error { // filter any challenge event logs in the block logs, err := d.fetchChallengeLogs(ctx, l1, block) if err != nil { @@ -295,7 +336,7 @@ }   for _, log := range logs { i := log.TxIndex - status, comm, err := d.decodeChallengeStatus(log) + status, comm, bn, err := d.decodeChallengeStatus(log) if err != nil { d.log.Error("failed to decode challenge event", "block", block.Number, "tx", i, "log", log.Index, "err", err) continue @@ -320,6 +361,7 @@ if tx.Hash() != log.TxHash { d.log.Error("tx hash mismatch", "block", block.Number, "txIdx", i, "log", log.Index, "txHash", tx.Hash(), "receiptTxHash", log.TxHash) continue } + var input []byte if d.cfg.CommitmentType == Keccak256CommitmentType { // Decode the input from resolver tx calldata @@ -333,13 +375,19 @@ d.log.Error("failed to verify commitment", "block", block.Number, "txIdx", i, "err", err) continue } } - d.log.Info("challenge resolved", "block", block, "txIdx", i, "comm", comm.Encode()) - d.state.SetResolvedChallenge(comm.Encode(), input, log.BlockNumber) + + d.log.Info("challenge resolved", "block", block, "txIdx", i) + // Resolve challenge in state + if err := d.state.ResolveChallenge(comm, block, bn, input); err != nil { + d.log.Error("failed to resolve challenge", "block", block.Number, "txIdx", i, "err", err) + continue + } case ChallengeActive: - d.log.Info("detected new active challenge", "block", block, "comm", comm.Encode()) - d.state.SetActiveChallenge(comm.Encode(), log.BlockNumber, d.cfg.ResolveWindow) + // create challenge in state + d.log.Info("detected new active challenge", "block", block, "comm", comm) + d.state.CreateChallenge(comm, block, bn) default: - d.log.Warn("skipping unknown challenge status", "block", block.Number, "tx", i, "log", log.Index, "status", status, "comm", comm.Encode()) + d.log.Warn("skipping unknown challenge status", "block", block.Number, "tx", i, "log", log.Index, "status", status, "comm", comm) } } return nil @@ -374,25 +422,17 @@ return logs, nil }   // decodeChallengeStatus decodes and validates a challenge event from a transaction log, returning the associated commitment bytes. -func (d *DA) decodeChallengeStatus(log *types.Log) (ChallengeStatus, CommitmentData, error) { +func (d *DA) decodeChallengeStatus(log *types.Log) (ChallengeStatus, CommitmentData, uint64, error) { event, err := DecodeChallengeStatusEvent(log) if err != nil { - return 0, nil, err + return 0, nil, 0, err } comm, err := DecodeCommitmentData(event.ChallengedCommitment) if err != nil { - return 0, nil, err + return 0, nil, 0, err } d.log.Debug("decoded challenge status event", "log", log, "event", event, "comm", fmt.Sprintf("%x", comm.Encode())) - - bn := event.ChallengedBlockNumber.Uint64() - // IsTracking just validates whether the commitment was challenged for the correct block number - // if it has been loaded from the batcher inbox before. Spam commitments will be tracked but - // ignored and evicted unless derivation encounters the commitment. - if !d.state.IsTracking(comm.Encode(), bn) { - return 0, nil, fmt.Errorf("%w: %x at block %d", ErrInvalidChallenge, comm.Encode(), bn) - } - return ChallengeStatus(event.Status), comm, nil + return ChallengeStatus(event.Status), comm, event.ChallengedBlockNumber.Uint64(), nil }   var (
diff --git OP/op-plasma/damgr_test.go CELO/op-plasma/damgr_test.go index 5a37badc19ca0d14b40a0c6ae3abbe2dadf91003..4288a6d53abe5659993d1b40e93062f976dd45df 100644 --- OP/op-plasma/damgr_test.go +++ CELO/op-plasma/damgr_test.go @@ -21,241 +21,178 @@ rng.Read(out) return out }   -// TestDAChallengeState is a simple test with small values to verify the finalized head logic -func TestDAChallengeState(t *testing.T) { - logger := testlog.Logger(t, log.LvlDebug) - - rng := rand.New(rand.NewSource(1234)) - state := NewState(logger, &NoopMetrics{}) - - i := uint64(1) - - challengeWindow := uint64(6) - resolveWindow := uint64(6) - - // track commitments in the first 10 blocks - for ; i < 10; i++ { - // this is akin to stepping the derivation pipeline through a range a blocks each with a commitment - state.SetInputCommitment(RandomData(rng, 32), i, challengeWindow) - } - - // blocks are finalized after the challenge window expires - bn, err := state.ExpireChallenges(10) - require.NoError(t, err) - // finalized head = 10 - 6 = 4 - require.Equal(t, uint64(4), bn) +func RandomCommitment(rng *rand.Rand) CommitmentData { + return NewKeccak256Commitment(RandomData(rng, 32)) +}   - // track the next commitment and mark it as challenged - c := RandomData(rng, 32) - // add input commitment at block i = 10 - state.SetInputCommitment(c, 10, challengeWindow) - // i+4 is the block at which it was challenged - state.SetActiveChallenge(c, 14, resolveWindow) +func l1Ref(n uint64) eth.L1BlockRef { + return eth.L1BlockRef{Number: n} +}   - for j := i + 1; j < 18; j++ { - // continue walking the pipeline through some more blocks with commitments - state.SetInputCommitment(RandomData(rng, 32), j, challengeWindow) - } - - // finalized l1 origin should not extend past the resolve window - bn, err = state.ExpireChallenges(18) - require.NoError(t, err) - // finalized is active_challenge_block - 1 = 10 - 1 and cannot move until the challenge expires - require.Equal(t, uint64(9), bn) +func bID(n uint64) eth.BlockID { + return eth.BlockID{Number: n} +}   - // walk past the resolve window - for j := uint64(18); j < 22; j++ { - state.SetInputCommitment(RandomData(rng, 32), j, challengeWindow) +// TestFinalization checks that the finalized L1 block ref is returned correctly when pruning with and without challenges +func TestFinalization(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + cfg := Config{ + ResolveWindow: 6, + ChallengeWindow: 6, } - - // no more active challenges, the finalized head can catch up to the challenge window - bn, err = state.ExpireChallenges(22) - require.ErrorIs(t, err, ErrReorgRequired) - // finalized head is now 22 - 6 = 16 - require.Equal(t, uint64(16), bn) + rng := rand.New(rand.NewSource(1234)) + state := NewState(logger, &NoopMetrics{}, cfg)   - // cleanup state we don't need anymore - state.Prune(22) - // now if we expire the challenges again, it won't request a reorg again - bn, err = state.ExpireChallenges(22) - require.NoError(t, err) - // finalized head hasn't moved - require.Equal(t, uint64(16), bn) + c1 := RandomCommitment(rng) + bn1 := uint64(2)   - // add one more commitment and challenge it - c = RandomData(rng, 32) - state.SetInputCommitment(c, 22, challengeWindow) - // challenge 3 blocks after - state.SetActiveChallenge(c, 25, resolveWindow) + // Track a commitment without a challenge + state.TrackCommitment(c1, l1Ref(bn1)) + require.NoError(t, state.ExpireCommitments(bID(7))) + require.Empty(t, state.expiredCommitments) + require.NoError(t, state.ExpireCommitments(bID(8))) + require.Empty(t, state.commitments)   - // exceed the challenge window with more commitments - for j := uint64(23); j < 30; j++ { - state.SetInputCommitment(RandomData(rng, 32), j, challengeWindow) - } + state.Prune(bID(bn1)) + require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment) + state.Prune(bID(7)) + require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment) + state.Prune(bID(8)) + require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment)   - // finalized head should not extend past the resolve window - bn, err = state.ExpireChallenges(30) - require.NoError(t, err) - // finalized head is stuck waiting for resolve window - require.Equal(t, uint64(21), bn) + // Track a commitment, challenge it, & then resolve it + c2 := RandomCommitment(rng) + bn2 := uint64(20) + state.TrackCommitment(c2, l1Ref(bn2)) + require.Equal(t, ChallengeUninitialized, state.GetChallengeStatus(c2, bn2)) + state.CreateChallenge(c2, bID(24), bn2) + require.Equal(t, ChallengeActive, state.GetChallengeStatus(c2, bn2)) + require.NoError(t, state.ResolveChallenge(c2, bID(30), bn2, nil)) + require.Equal(t, ChallengeResolved, state.GetChallengeStatus(c2, bn2))   - input := RandomData(rng, 100) - // resolve the challenge - state.SetResolvedChallenge(c, input, 30) + // Expire Challenges & Comms after challenge period but before resolve end & assert they are not expired yet + require.NoError(t, state.ExpireCommitments(bID(28))) + require.Empty(t, state.expiredCommitments) + state.ExpireChallenges(bID(28)) + require.Empty(t, state.expiredChallenges)   - // finalized head catches up - bn, err = state.ExpireChallenges(31) - require.NoError(t, err) - // finalized head is now 31 - 6 = 25 - require.Equal(t, uint64(25), bn) + // Now fully expire them + require.NoError(t, state.ExpireCommitments(bID(30))) + require.Empty(t, state.commitments) + state.ExpireChallenges(bID(30)) + require.Empty(t, state.challenges)   - // the resolved input is also stored - storedInput, err := state.GetResolvedInput(c) - require.NoError(t, err) - require.Equal(t, input, storedInput) + // Now finalize everything + state.Prune(bID(20)) + require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment) + state.Prune(bID(28)) + require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment) + state.Prune(bID(32)) + require.Equal(t, eth.L1BlockRef{Number: bn2}, state.lastPrunedCommitment) }   // TestExpireChallenges expires challenges and prunes the state for longer windows // with commitments every 6 blocks. func TestExpireChallenges(t *testing.T) { - logger := testlog.Logger(t, log.LvlDebug) + logger := testlog.Logger(t, log.LevelInfo) + + cfg := Config{ + ResolveWindow: 90, + ChallengeWindow: 90, + }   rng := rand.New(rand.NewSource(1234)) - state := NewState(logger, &NoopMetrics{}) + state := NewState(logger, &NoopMetrics{}, cfg)   - comms := make(map[uint64][]byte) + comms := make(map[uint64]CommitmentData)   i := uint64(3713854)   - var finalized uint64 - - challengeWindow := uint64(90) - resolveWindow := uint64(90) - // increment new commitments every 6 blocks for ; i < 3713948; i += 6 { - comm := RandomData(rng, 32) + comm := RandomCommitment(rng) comms[i] = comm - logger.Info("set commitment", "block", i) - cm := state.GetOrTrackChallenge(comm, i, challengeWindow) - require.NotNil(t, cm) + logger.Info("set commitment", "block", i, "comm", comm) + state.TrackCommitment(comm, l1Ref(i))   - bn, err := state.ExpireChallenges(i) - logger.Info("expire challenges", "finalized head", bn, "err", err) - - // only update finalized head if it has moved - if bn > finalized { - finalized = bn - // prune unused state - state.Prune(bn) - } + require.NoError(t, state.ExpireCommitments(bID(i))) + state.ExpireChallenges(bID(i)) }   // activate a couple of subsequent challenges - state.SetActiveChallenge(comms[3713926], 3713948, resolveWindow) - - state.SetActiveChallenge(comms[3713932], 3713950, resolveWindow) + state.CreateChallenge(comms[3713926], bID(3713948), 3713926) + state.CreateChallenge(comms[3713932], bID(3713950), 3713932)   // continue incrementing commitments for ; i < 3714038; i += 6 { - comm := RandomData(rng, 32) + comm := RandomCommitment(rng) comms[i] = comm logger.Info("set commitment", "block", i) - cm := state.GetOrTrackChallenge(comm, i, challengeWindow) - require.NotNil(t, cm) - - bn, err := state.ExpireChallenges(i) - logger.Info("expire challenges", "expired", bn, "err", err) - - if bn > finalized { - finalized = bn - state.Prune(bn) - } - - } - - // finalized head does not move as it expires previously seen blocks - bn, err := state.ExpireChallenges(3714034) - require.NoError(t, err) - require.Equal(t, uint64(3713920), bn) - - bn, err = state.ExpireChallenges(3714035) - require.NoError(t, err) - require.Equal(t, uint64(3713920), bn) - - bn, err = state.ExpireChallenges(3714036) - require.NoError(t, err) - require.Equal(t, uint64(3713920), bn) - - bn, err = state.ExpireChallenges(3714037) - require.NoError(t, err) - require.Equal(t, uint64(3713920), bn) - - // lastly we get to the resolve window and trigger a reorg - _, err = state.ExpireChallenges(3714038) - require.ErrorIs(t, err, ErrReorgRequired) + state.TrackCommitment(comm, l1Ref(i))   - // this is simulating a pipeline reset where it walks back challenge + resolve window - for i := uint64(3713854); i < 3714044; i += 6 { - cm := state.GetOrTrackChallenge(comms[i], i, challengeWindow) - require.NotNil(t, cm) - - // check that the challenge status was updated to expired - if i == 3713926 { - require.Equal(t, ChallengeExpired, cm.challengeStatus) - } + require.NoError(t, state.ExpireCommitments(bID(i))) + state.ExpireChallenges(bID(i)) }   - bn, err = state.ExpireChallenges(3714038) - require.NoError(t, err) - - // finalized at last - require.Equal(t, uint64(3713926), bn) + // Jump ahead to the end of the resolve window for comm included in block 3713926 which triggers a reorg + state.ExpireChallenges(bID(3714106)) + require.ErrorIs(t, state.ExpireCommitments(bID(3714106)), ErrReorgRequired) }   +// TestDAChallengeDetached tests the lookahead + reorg handling of the da state func TestDAChallengeDetached(t *testing.T) { - logger := testlog.Logger(t, log.LvlDebug) + logger := testlog.Logger(t, log.LevelWarn) + + cfg := Config{ + ResolveWindow: 6, + ChallengeWindow: 6, + }   rng := rand.New(rand.NewSource(1234)) - state := NewState(logger, &NoopMetrics{}) + state := NewState(logger, &NoopMetrics{}, cfg)   - challengeWindow := uint64(6) - resolveWindow := uint64(6) - - c1 := RandomData(rng, 32) - c2 := RandomData(rng, 32) + c1 := RandomCommitment(rng) + c2 := RandomCommitment(rng)   // c1 at bn1 is missing, pipeline stalls - state.GetOrTrackChallenge(c1, 1, challengeWindow) + state.TrackCommitment(c1, l1Ref(1))   // c2 at bn2 is challenged at bn3 - require.True(t, state.IsTracking(c2, 2)) - state.SetActiveChallenge(c2, 3, resolveWindow) + state.CreateChallenge(c2, bID(3), uint64(2)) + require.Equal(t, ChallengeActive, state.GetChallengeStatus(c2, uint64(2)))   // c1 is finally challenged at bn5 - state.SetActiveChallenge(c1, 5, resolveWindow) + state.CreateChallenge(c1, bID(5), uint64(1))   - // c2 expires but should not trigger a reset because we don't know if it's valid yet - bn, err := state.ExpireChallenges(10) + // c2 expires but should not trigger a reset because we're waiting for c1 to expire + state.ExpireChallenges(bID(10)) + err := state.ExpireCommitments(bID(10)) require.NoError(t, err) - require.Equal(t, uint64(0), bn)   // c1 expires finally - bn, err = state.ExpireChallenges(11) + state.ExpireChallenges(bID(11)) + err = state.ExpireCommitments(bID(11)) require.ErrorIs(t, err, ErrReorgRequired) - require.Equal(t, uint64(1), bn)   - // pruning finalized block is safe - state.Prune(bn) + // pruning finalized block is safe. It should not prune any commitments yet. + state.Prune(bID(1)) + require.Equal(t, eth.L1BlockRef{}, state.lastPrunedCommitment)   - // pipeline discovers c2 - comm := state.GetOrTrackChallenge(c2, 2, challengeWindow) + // Perform reorg back to bn2 + state.ClearCommitments() + + // pipeline discovers c2 at bn2 + state.TrackCommitment(c2, l1Ref(2)) // it is already marked as expired so it will be skipped without needing a reorg - require.Equal(t, ChallengeExpired, comm.challengeStatus) + require.Equal(t, ChallengeExpired, state.GetChallengeStatus(c2, uint64(2)))   // later when we get to finalizing block 10 + margin, the pending challenge is safely pruned - state.Prune(210) - require.Equal(t, 0, len(state.expiredComms)) + // Note: We need to go through the expire then prune steps + state.ExpireChallenges(bID(201)) + err = state.ExpireCommitments(bID(201)) + require.ErrorIs(t, err, ErrReorgRequired) + state.Prune(bID(201)) + require.True(t, state.NoCommitments()) }   // cannot import from testutils at this time because of import cycle @@ -290,11 +227,12 @@ func (m *mockL1Fetcher) ExpectL1BlockRefByNumber(num uint64, ref eth.L1BlockRef, err error) { m.Mock.On("L1BlockRefByNumber", num).Once().Return(ref, err) }   -func TestFilterInvalidBlockNumber(t *testing.T) { - logger := testlog.Logger(t, log.LevelDebug) +func TestAdvanceChallengeOrigin(t *testing.T) { + logger := testlog.Logger(t, log.LevelWarn) ctx := context.Background()   l1F := &mockL1Fetcher{} + defer l1F.AssertExpectations(t)   storage := NewMockDAClient(logger)   @@ -303,10 +241,12 @@ pcfg := Config{ ChallengeWindow: 90, ResolveWindow: 90, DAChallengeContractAddress: daddr, }   + bhash := common.HexToHash("0xd438144ffab918b1349e7cd06889c26800c26d8edc34d64f750e3e097166a09c") + bhash2 := common.HexToHash("0xd000004ffab918b1349e7cd06889c26800c26d8edc34d64f750e3e097166a09c") bn := uint64(19) - bhash := common.HexToHash("0xd438144ffab918b1349e7cd06889c26800c26d8edc34d64f750e3e097166a09c") + comm := Keccak256Commitment(common.FromHex("eed82c1026bdd0f23461dd6ca515ef677624e63e6fc0ff91e3672af8eddf579d"))   - state := NewState(logger, &NoopMetrics{}) + state := NewState(logger, &NoopMetrics{}, pcfg)   da := NewPlasmaDAWithState(logger, pcfg, storage, &NoopMetrics{}, state)   @@ -339,28 +279,26 @@ Hash: bhash, } l1F.ExpectFetchReceipts(bhash, nil, receipts, nil)   - // we get 1 log successfully filtered as valid status updated contract event - logs, err := da.fetchChallengeLogs(ctx, l1F, id) - require.NoError(t, err) - require.Equal(t, len(logs), 1) - - // commitment is tracked but not canonical - status, comm, err := da.decodeChallengeStatus(logs[0]) + // Advance the challenge origin & ensure that we track the challenge + err := da.AdvanceChallengeOrigin(ctx, l1F, id) require.NoError(t, err)   - c, has := state.commsByKey[string(comm.Encode())] + c, has := state.GetChallenge(comm, 14) require.True(t, has) - require.False(t, c.canonical) + require.Equal(t, ChallengeActive, c.challengeStatus)   - require.Equal(t, ChallengeActive, status) - // once tracked, set as active based on decoded status - state.SetActiveChallenge(comm.Encode(), bn, pcfg.ResolveWindow) - - // once we request it during derivation it becomes canonical - tracked := state.GetOrTrackChallenge(comm.Encode(), 14, pcfg.ChallengeWindow) - require.True(t, tracked.canonical) + // Advance the challenge origin until the challenge should be expired + for i := bn + 1; i < bn+1+pcfg.ChallengeWindow; i++ { + id2 := eth.BlockID{ + Number: i, + Hash: bhash2, + } + l1F.ExpectFetchReceipts(bhash2, nil, nil, nil) + err = da.AdvanceChallengeOrigin(ctx, l1F, id2) + require.NoError(t, err) + } + state.Prune(bID(bn + 1 + pcfg.ChallengeWindow + pcfg.ResolveWindow))   - require.Equal(t, ChallengeActive, tracked.challengeStatus) - require.Equal(t, uint64(14), tracked.blockNumber) - require.Equal(t, bn+pcfg.ResolveWindow, tracked.expiresAt) + _, has = state.GetChallenge(comm, 14) + require.False(t, has) }
diff --git OP/op-plasma/damock.go CELO/op-plasma/damock.go index c43bdbc53e92a85030cdb969a2a5927cb0c5ed74..0a1e40d0c6b16e0eafe0811f38fcbe7111cba741 100644 --- OP/op-plasma/damock.go +++ CELO/op-plasma/damock.go @@ -82,7 +82,7 @@ // PlasmaDisabled is a noop plasma DA implementation for stubbing. type PlasmaDisabled struct{}   -func (d *PlasmaDisabled) GetInput(ctx context.Context, l1 L1Fetcher, commitment CommitmentData, blockId eth.BlockID) (eth.Data, error) { +func (d *PlasmaDisabled) GetInput(ctx context.Context, l1 L1Fetcher, commitment CommitmentData, blockId eth.L1BlockRef) (eth.Data, error) { return nil, ErrNotEnabled }
diff --git OP/op-plasma/dastate.go CELO/op-plasma/dastate.go index c6fee4800af9474dc1a958b00363df14b29b5b06..01515e293fd31eaae5ecf564e4eed95534cd1f2b 100644 --- OP/op-plasma/dastate.go +++ CELO/op-plasma/dastate.go @@ -1,10 +1,10 @@ package plasma   import ( - "container/heap" "errors" "fmt"   + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/log" )   @@ -23,206 +23,236 @@ )   // Commitment keeps track of the onchain state of an input commitment. type Commitment struct { - key []byte // the encoded commitment - input []byte // the input itself if it was resolved onchain - expiresAt uint64 // represents the block number after which the commitment can no longer be challenged or if challenged no longer be resolved. - blockNumber uint64 // block where the commitment is included as calldata to the batcher inbox - challengeStatus ChallengeStatus // latest known challenge status - canonical bool // whether the commitment was derived as part of the canonical chain if canonical it will be in comms queue if not in the pendingComms queue. -} - -// CommQueue is a priority queue of commitments ordered by block number. -type CommQueue []*Commitment - -var _ heap.Interface = (*CommQueue)(nil) - -func (c CommQueue) Len() int { return len(c) } - -// we want the first item in the queue to have the lowest block number -func (c CommQueue) Less(i, j int) bool { - return c[i].blockNumber < c[j].blockNumber + data CommitmentData + inclusionBlock eth.L1BlockRef // block where the commitment is included as calldata to the batcher inbox. + challengeWindowEnd uint64 // represents the block number after which the commitment can no longer be challenged. }   -func (c CommQueue) Swap(i, j int) { - c[i], c[j] = c[j], c[i] +// Challenges are used to track the status of a challenge against a commitment. +type Challenge struct { + commData CommitmentData // the specific commitment which was challenged + commInclusionBlockNumber uint64 // block where the commitment is included as calldata to the batcher inbox + resolveWindowEnd uint64 // block number at which the challenge must be resolved by + input []byte // the input itself if it was resolved onchain + challengeStatus ChallengeStatus // status of the challenge based on the highest processed action }   -func (c *CommQueue) Push(x any) { - *c = append(*c, x.(*Commitment)) +func (c *Challenge) key() string { + return challengeKey(c.commData, c.commInclusionBlockNumber) }   -func (c *CommQueue) Pop() any { - old := *c - n := len(old) - item := old[n-1] - old[n-1] = nil // avoid memory leak - *c = old[0 : n-1] - return item +func challengeKey(comm CommitmentData, inclusionBlockNumber uint64) string { + return fmt.Sprintf("%d%x", inclusionBlockNumber, comm.Encode()) }   // State tracks the commitment and their challenges in order of l1 inclusion. +// Commitments and Challenges are tracked in L1 inclusion order. They are tracked in two separate queues for Active and Expired commitments. +// When commitments are moved to Expired, if there is an active challenge, the DA Manager is informed that a commitment became invalid. +// Challenges and Commitments can be pruned when they are beyond a certain block number (e.g. when they are finalized). +// In the special case of a L2 reorg, challenges are still tracked but commitments are removed. +// This will allow the plasma fetcher to find the expired challenge. type State struct { - activeComms CommQueue - expiredComms CommQueue - commsByKey map[string]*Commitment - log log.Logger - metrics Metricer - finalized uint64 + commitments []Commitment // commitments where the challenge/resolve period has not expired yet + expiredCommitments []Commitment // commitments where the challenge/resolve period has expired but not finalized + challenges []*Challenge // challenges ordered by L1 inclusion + expiredChallenges []*Challenge // challenges ordered by L1 inclusion + challengesMap map[string]*Challenge // challenges by seralized comm + block number for easy lookup + lastPrunedCommitment eth.L1BlockRef // the last commitment to be pruned + cfg Config + log log.Logger + metrics Metricer }   -func NewState(log log.Logger, m Metricer) *State { +func NewState(log log.Logger, m Metricer, cfg Config) *State { return &State{ - activeComms: make(CommQueue, 0), - expiredComms: make(CommQueue, 0), - commsByKey: make(map[string]*Commitment), - log: log, - metrics: m, + commitments: make([]Commitment, 0), + expiredCommitments: make([]Commitment, 0), + challenges: make([]*Challenge, 0), + expiredChallenges: make([]*Challenge, 0), + challengesMap: make(map[string]*Challenge), + cfg: cfg, + log: log, + metrics: m, } }   -// IsTracking returns whether we currently have a commitment for the given key. -// if the block number is mismatched we return false to ignore the challenge. -func (s *State) IsTracking(key []byte, bn uint64) bool { - if c, ok := s.commsByKey[string(key)]; ok { - return c.blockNumber == bn - } - // track the commitment knowing we may be in detached head and not have seen - // the commitment in the inbox yet. - s.TrackDetachedCommitment(key, bn) - return true +// ClearCommitments removes all tracked commitments but not challenges. +// This should be used to retain the challenge state when performing a L2 reorg +func (s *State) ClearCommitments() { + s.commitments = s.commitments[:0] + s.expiredCommitments = s.expiredCommitments[:0] }   -// TrackDetachedCommitment is used for indexing challenges for commitments that have not yet -// been derived due to the derivation pipeline being stalled pending a commitment to be challenged. -// Memory usage is bound to L1 block space during the DA windows, so it is hard and expensive to spam. -// Note that the challenge status and expiration is updated separately after it is tracked. -func (s *State) TrackDetachedCommitment(key []byte, bn uint64) { - c := &Commitment{ - key: key, - expiresAt: bn, - blockNumber: bn, - canonical: false, - } - s.log.Debug("tracking detached commitment", "blockNumber", c.blockNumber, "commitment", fmt.Sprintf("%x", key)) - heap.Push(&s.activeComms, c) - s.commsByKey[string(key)] = c +// Reset clears the state. It should be used when a L1 reorg occurs. +func (s *State) Reset() { + s.commitments = s.commitments[:0] + s.expiredCommitments = s.expiredCommitments[:0] + s.challenges = s.challenges[:0] + s.expiredChallenges = s.expiredChallenges[:0] + clear(s.challengesMap) }   -// SetActiveChallenge switches the state of a given commitment to active challenge. Noop if -// the commitment is not tracked as we don't want to track challenges for invalid commitments. -func (s *State) SetActiveChallenge(key []byte, challengedAt uint64, resolveWindow uint64) { - if c, ok := s.commsByKey[string(key)]; ok { - c.expiresAt = challengedAt + resolveWindow - c.challengeStatus = ChallengeActive - s.metrics.RecordActiveChallenge(c.blockNumber, challengedAt, key) +// CreateChallenge creates & tracks a challenge. It will overwrite earlier challenges if the +// same commitment is challenged again. +func (s *State) CreateChallenge(comm CommitmentData, inclusionBlock eth.BlockID, commBlockNumber uint64) { + c := &Challenge{ + commData: comm, + commInclusionBlockNumber: commBlockNumber, + resolveWindowEnd: inclusionBlock.Number + s.cfg.ResolveWindow, + challengeStatus: ChallengeActive, } + s.challenges = append(s.challenges, c) + s.challengesMap[c.key()] = c }   -// SetResolvedChallenge switches the state of a given commitment to resolved. Noop if -// the commitment is not tracked as we don't want to track challenges for invalid commitments. -// The input posted onchain is stored in the state for later retrieval. -func (s *State) SetResolvedChallenge(key []byte, input []byte, resolvedAt uint64) { - if c, ok := s.commsByKey[string(key)]; ok { - c.challengeStatus = ChallengeResolved - c.expiresAt = resolvedAt - c.input = input - s.metrics.RecordResolvedChallenge(key) +// ResolveChallenge marks a challenge as resolved. It will return an error if there was not a corresponding challenge. +func (s *State) ResolveChallenge(comm CommitmentData, inclusionBlock eth.BlockID, commBlockNumber uint64, input []byte) error { + c, ok := s.challengesMap[challengeKey(comm, commBlockNumber)] + if !ok { + return errors.New("challenge was not tracked") } + c.input = input + c.challengeStatus = ChallengeResolved + return nil }   -// SetInputCommitment initializes a new commitment and adds it to the state. -// This is called when we see a commitment during derivation so we can refer to it later in -// challenges. -func (s *State) SetInputCommitment(key []byte, committedAt uint64, challengeWindow uint64) *Commitment { - c := &Commitment{ - key: key, - expiresAt: committedAt + challengeWindow, - blockNumber: committedAt, - canonical: true, +// TrackCommitment stores a commitment in the State +func (s *State) TrackCommitment(comm CommitmentData, inclusionBlock eth.L1BlockRef) { + c := Commitment{ + data: comm, + inclusionBlock: inclusionBlock, + challengeWindowEnd: inclusionBlock.Number + s.cfg.ChallengeWindow, } - s.log.Debug("append commitment", "expiresAt", c.expiresAt, "blockNumber", c.blockNumber) - heap.Push(&s.activeComms, c) - s.commsByKey[string(key)] = c + s.commitments = append(s.commitments, c) +}   - return c +// GetChallenge looks up a challenge against commitment + inclusion block. +func (s *State) GetChallenge(comm CommitmentData, commBlockNumber uint64) (*Challenge, bool) { + challenge, ok := s.challengesMap[challengeKey(comm, commBlockNumber)] + return challenge, ok }   -// GetOrTrackChallenge returns the commitment for the given key if it is already tracked, or -// initializes a new commitment and adds it to the state. -func (s *State) GetOrTrackChallenge(key []byte, bn uint64, challengeWindow uint64) *Commitment { - if c, ok := s.commsByKey[string(key)]; ok { - // commitments previously introduced by challenge events are marked as canonical - c.canonical = true - return c +// GetChallengeStatus looks up a challenge's status, or returns ChallengeUninitialized if there is no challenge. +func (s *State) GetChallengeStatus(comm CommitmentData, commBlockNumber uint64) ChallengeStatus { + challenge, ok := s.GetChallenge(comm, commBlockNumber) + if ok { + return challenge.challengeStatus } - return s.SetInputCommitment(key, bn, challengeWindow) + return ChallengeUninitialized }   -// GetResolvedInput returns the input bytes if the commitment was resolved onchain. -func (s *State) GetResolvedInput(key []byte) ([]byte, error) { - if c, ok := s.commsByKey[string(key)]; ok { - return c.input, nil - } - return nil, errors.New("commitment not found") +// NoCommitments returns true iff it is not tracking any commitments or challenges. +func (s *State) NoCommitments() bool { + return len(s.challenges) == 0 && len(s.expiredChallenges) == 0 && len(s.commitments) == 0 && len(s.expiredCommitments) == 0 }   -// ExpireChallenges walks back from the oldest commitment to find the latest l1 origin -// for which input data can no longer be challenged. It also marks any active challenges -// as expired based on the new latest l1 origin. If any active challenges are expired -// it returns an error to signal that a derivation pipeline reset is required. -func (s *State) ExpireChallenges(bn uint64) (uint64, error) { +// ExpireCommitments moves commitments from the acive state map to the expired state map. +// commitments are considered expired when the challenge window ends without a challenge, or when the resolve window ends without a resolution to the challenge. +// This function processess commitments in order of inclusion until it finds a commitment which has not expired. +// If a commitment expires which did not resolve its challenge, it returns ErrReorgRequired to indicate that a L2 reorg should be performed. +func (s *State) ExpireCommitments(origin eth.BlockID) error { var err error - for s.activeComms.Len() > 0 && s.activeComms[0].expiresAt <= bn && s.activeComms[0].blockNumber > s.finalized { - // move from the active to the expired queue - c := heap.Pop(&s.activeComms).(*Commitment) - heap.Push(&s.expiredComms, c) + for len(s.commitments) > 0 { + c := s.commitments[0] + challenge, ok := s.GetChallenge(c.data, c.inclusionBlock.Number) + + // A commitment expires when the challenge window ends without a challenge, + // or when the resolve window on the challenge ends. + expiresAt := c.challengeWindowEnd + if ok { + expiresAt = challenge.resolveWindowEnd + }   - if c.canonical { - // advance finalized head only if the commitment was derived as part of the canonical chain - s.finalized = c.blockNumber + // If the commitment expires the in future, return early + if expiresAt > origin.Number { + return err }   - // active mark as expired so it is skipped in the derivation pipeline + // If it has expired, move the commitment to the expired queue + s.log.Info("Expiring commitment", "comm", c.data, "commInclusionBlockNumber", c.inclusionBlock.Number, "origin", origin, "challenged", ok) + s.expiredCommitments = append(s.expiredCommitments, c) + s.commitments = s.commitments[1:] + + // If the expiring challenge was not resolved, return an error to indicate a reorg is required. + if ok && challenge.challengeStatus != ChallengeResolved { + err = ErrReorgRequired + } + } + return err +} + +// ExpireChallenges moves challenges from the active state map to the expired state map. +// challenges are considered expired when the oirgin is beyond the challenge's resolve window. +// This function processess challenges in order of inclusion until it finds a commitment which has not expired. +// This function must be called for every block because there is no contract event to expire challenges. +func (s *State) ExpireChallenges(origin eth.BlockID) { + for len(s.challenges) > 0 { + c := s.challenges[0] + + // If the challenge can still be resolved, return early + if c.resolveWindowEnd > origin.Number { + return + } + + // Move the challenge to the expired queue + s.log.Info("Expiring challenge", "comm", c.commData, "commInclusionBlockNumber", c.commInclusionBlockNumber, "origin", origin) + s.expiredChallenges = append(s.expiredChallenges, c) + s.challenges = s.challenges[1:] + + // Mark the challenge as expired if it was not resolved if c.challengeStatus == ChallengeActive { c.challengeStatus = ChallengeExpired - - // only reorg if canonical. If the pipeline is behind, it will just - // get skipped once it catches up. If it is spam, it will be pruned - // with no effect. - if c.canonical { - err = ErrReorgRequired - s.metrics.RecordExpiredChallenge(c.key) - } } } +}   - return s.finalized, err +// Prune removes challenges & commitments which have an expiry block number beyond the given block number. +func (s *State) Prune(origin eth.BlockID) { + // Commitments rely on challenges, so we prune commitments first. + s.pruneCommitments(origin) + s.pruneChallenges(origin) }   -// safely prune in case reset is deeper than the finalized l1 -const commPruneMargin = 200 +// pruneCommitments removes commitments which have are beyond a given block number. +// It will remove commitments in order of inclusion until it finds a commitment which is not beyond the given block number. +func (s *State) pruneCommitments(origin eth.BlockID) { + for len(s.expiredCommitments) > 0 { + c := s.expiredCommitments[0] + challenge, ok := s.GetChallenge(c.data, c.inclusionBlock.Number)   -// Prune removes commitments once they can no longer be challenged or resolved. -// the finalized head block number is passed so we can safely remove any commitments -// with finalized block numbers. -func (s *State) Prune(bn uint64) { - if bn > commPruneMargin { - bn -= commPruneMargin - } else { - bn = 0 - } - for s.expiredComms.Len() > 0 && s.expiredComms[0].blockNumber < bn { - c := heap.Pop(&s.expiredComms).(*Commitment) - s.log.Debug("prune commitment", "expiresAt", c.expiresAt, "blockNumber", c.blockNumber) - delete(s.commsByKey, string(c.key)) + // the commitment is considered removable when the challenge window ends without a challenge, + // or when the resolve window on the challenge ends. + expiresAt := c.challengeWindowEnd + if ok { + expiresAt = challenge.resolveWindowEnd + } + + // If the commitment is not beyond the given block number, return early + if expiresAt > origin.Number { + break + } + + // Remove the commitment + s.expiredCommitments = s.expiredCommitments[1:] + + // Record the latest inclusion block to be returned + s.lastPrunedCommitment = c.inclusionBlock } }   -// In case of L1 reorg, state should be cleared so we can sync all the challenge events -// from scratch. -func (s *State) Reset() { - s.activeComms = s.activeComms[:0] - s.expiredComms = s.expiredComms[:0] - s.finalized = 0 - clear(s.commsByKey) +// pruneChallenges removes challenges which have are beyond a given block number. +// It will remove challenges in order of inclusion until it finds a challenge which is not beyond the given block number. +func (s *State) pruneChallenges(origin eth.BlockID) { + for len(s.expiredChallenges) > 0 { + c := s.expiredChallenges[0] + + // If the challenge is not beyond the given block number, return early + if c.resolveWindowEnd > origin.Number { + break + } + + // Remove the challenge + s.expiredChallenges = s.expiredChallenges[1:] + delete(s.challengesMap, c.key()) + } }
diff --git OP/op-program/client/claim/validate.go CELO/op-program/client/claim/validate.go new file mode 100644 index 0000000000000000000000000000000000000000..05655208c6e78a7b4eef38e7a2b908669bd47a5d --- /dev/null +++ CELO/op-program/client/claim/validate.go @@ -0,0 +1,34 @@ +package claim + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +var ErrClaimNotValid = errors.New("invalid claim") + +type L2Source interface { + L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) + L2OutputRoot(uint64) (eth.Bytes32, error) +} + +func ValidateClaim(log log.Logger, l2ClaimBlockNum uint64, claimedOutputRoot eth.Bytes32, src L2Source) error { + l2Head, err := src.L2BlockRefByLabel(context.Background(), eth.Safe) + if err != nil { + return fmt.Errorf("cannot retrieve safe head: %w", err) + } + outputRoot, err := src.L2OutputRoot(min(l2ClaimBlockNum, l2Head.Number)) + if err != nil { + return fmt.Errorf("calculate L2 output root: %w", err) + } + log.Info("Validating claim", "head", l2Head, "output", outputRoot, "claim", claimedOutputRoot) + if claimedOutputRoot != outputRoot { + return fmt.Errorf("%w: claim: %v actual: %v", ErrClaimNotValid, claimedOutputRoot, outputRoot) + } + return nil +}
diff --git OP/op-program/client/claim/validate_test.go CELO/op-program/client/claim/validate_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c8a9b263eba5299f1ca664152a6a76de804da6ad --- /dev/null +++ CELO/op-program/client/claim/validate_test.go @@ -0,0 +1,113 @@ +package claim + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +type mockL2 struct { + safeL2 eth.L2BlockRef + safeL2Err error + + outputRoot eth.Bytes32 + outputRootErr error + + requestedOutputRoot uint64 +} + +func (m *mockL2) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) { + if label != eth.Safe { + panic("unexpected usage") + } + if m.safeL2Err != nil { + return eth.L2BlockRef{}, m.safeL2Err + } + return m.safeL2, nil +} + +func (m *mockL2) L2OutputRoot(u uint64) (eth.Bytes32, error) { + m.requestedOutputRoot = u + if m.outputRootErr != nil { + return eth.Bytes32{}, m.outputRootErr + } + return m.outputRoot, nil +} + +var _ L2Source = (*mockL2)(nil) + +func TestValidateClaim(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + expected := eth.Bytes32{0x11} + l2 := &mockL2{ + outputRoot: expected, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(0), expected, l2) + require.NoError(t, err) + }) + + t.Run("Valid-PriorToSafeHead", func(t *testing.T) { + expected := eth.Bytes32{0x11} + l2 := &mockL2{ + outputRoot: expected, + safeL2: eth.L2BlockRef{ + Number: 10, + }, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(20), expected, l2) + require.NoError(t, err) + require.Equal(t, uint64(10), l2.requestedOutputRoot) + }) + + t.Run("Invalid", func(t *testing.T) { + l2 := &mockL2{ + outputRoot: eth.Bytes32{0x22}, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(0), eth.Bytes32{0x11}, l2) + require.ErrorIs(t, err, ErrClaimNotValid) + }) + + t.Run("Invalid-PriorToSafeHead", func(t *testing.T) { + l2 := &mockL2{ + outputRoot: eth.Bytes32{0x22}, + safeL2: eth.L2BlockRef{Number: 10}, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(20), eth.Bytes32{0x55}, l2) + require.ErrorIs(t, err, ErrClaimNotValid) + require.Equal(t, uint64(10), l2.requestedOutputRoot) + }) + + t.Run("Error-safe-head", func(t *testing.T) { + expectedErr := errors.New("boom") + l2 := &mockL2{ + outputRoot: eth.Bytes32{0x11}, + safeL2: eth.L2BlockRef{Number: 10}, + safeL2Err: expectedErr, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(0), eth.Bytes32{0x11}, l2) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("Error-output-root", func(t *testing.T) { + expectedErr := errors.New("boom") + l2 := &mockL2{ + outputRoot: eth.Bytes32{0x11}, + outputRootErr: expectedErr, + safeL2: eth.L2BlockRef{Number: 10}, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(0), eth.Bytes32{0x11}, l2) + require.ErrorIs(t, err, expectedErr) + }) +}
diff --git OP/op-program/client/driver/driver.go CELO/op-program/client/driver/driver.go index f533865e96dc50e614eb43947b38ab94d4c7e09c..7bcd8268476cded6669bd31d30113f567448d102 100644 --- OP/op-program/client/driver/driver.go +++ CELO/op-program/client/driver/driver.go @@ -3,167 +3,88 @@ import ( "context" "errors" - "fmt" - "io"   "github.com/ethereum/go-ethereum/log"   "github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-node/rollup/attributes" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" plasma "github.com/ethereum-optimism/optimism/op-plasma" - "github.com/ethereum-optimism/optimism/op-service/eth" )   -var ErrClaimNotValid = errors.New("invalid claim") - -type Derivation interface { - Step(ctx context.Context) error +type EndCondition interface { + Closing() bool + Result() error }   -type Pipeline interface { - Step(ctx context.Context, pendingSafeHead eth.L2BlockRef) (outAttrib *derive.AttributesWithParent, outErr error) - ConfirmEngineReset() -} +type Driver struct { + logger log.Logger   -type Engine interface { - SafeL2Head() eth.L2BlockRef - PendingSafeL2Head() eth.L2BlockRef - TryUpdateEngine(ctx context.Context) error - engine.ResetEngineControl -} + events []rollup.Event   -type L2Source interface { - engine.Engine - L2OutputRoot(uint64) (eth.Bytes32, error) + end EndCondition + deriver rollup.Deriver }   -type Deriver interface { - SafeL2Head() eth.L2BlockRef - SyncStep(ctx context.Context) error -} +func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher, + l1BlobsSource derive.L1BlobsFetcher, l2Source engine.Engine, targetBlockNum uint64) *Driver {   -type MinimalSyncDeriver struct { - logger log.Logger - pipeline Pipeline - attributesHandler driver.AttributesHandler - l1Source derive.L1Fetcher - l2Source L2Source - engine Engine - syncCfg *sync.Config - initialResetDone bool - cfg *rollup.Config -} + d := &Driver{ + logger: logger, + }   -func (d *MinimalSyncDeriver) SafeL2Head() eth.L2BlockRef { - return d.engine.SafeL2Head() -} + pipeline := derive.NewDerivationPipeline(logger, cfg, l1Source, l1BlobsSource, plasma.Disabled, l2Source, metrics.NoopMetrics) + pipelineDeriver := derive.NewPipelineDeriver(context.Background(), pipeline, d)   -func (d *MinimalSyncDeriver) SyncStep(ctx context.Context) error { - if !d.initialResetDone { - if err := d.engine.TryUpdateEngine(ctx); !errors.Is(err, engine.ErrNoFCUNeeded) { - return err - } - if err := engine.ResetEngine(ctx, d.logger, d.cfg, d.engine, d.l1Source, d.l2Source, d.syncCfg, nil); err != nil { - return err - } - d.pipeline.ConfirmEngineReset() - d.initialResetDone = true - } + ec := engine.NewEngineController(l2Source, logger, metrics.NoopMetrics, cfg, sync.CLSync, d) + engineDeriv := engine.NewEngDeriver(logger, context.Background(), cfg, ec, d) + syncCfg := &sync.Config{SyncMode: sync.CLSync} + engResetDeriv := engine.NewEngineResetDeriver(context.Background(), logger, cfg, l1Source, l2Source, syncCfg, d)   - if err := d.engine.TryUpdateEngine(ctx); !errors.Is(err, engine.ErrNoFCUNeeded) { - return err - } - if err := d.attributesHandler.Proceed(ctx); err != io.EOF { - // EOF error means we can't process the next attributes. Then we should derive the next attributes. - return err + prog := &ProgramDeriver{ + logger: logger, + Emitter: d, + closing: false, + result: nil, + targetBlockNum: targetBlockNum, }   - attrib, err := d.pipeline.Step(ctx, d.engine.PendingSafeL2Head()) - if err != nil { - return err + d.deriver = &rollup.SynchronousDerivers{ + prog, + engineDeriv, + pipelineDeriver, + engResetDeriv, } - d.attributesHandler.SetAttributes(attrib) - return nil -} - -type Driver struct { - logger log.Logger + d.end = prog   - deriver Deriver - - l2OutputRoot func(uint64) (eth.Bytes32, error) - targetBlockNum uint64 + return d }   -func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher, l1BlobsSource derive.L1BlobsFetcher, l2Source L2Source, targetBlockNum uint64) *Driver { - engine := engine.NewEngineController(l2Source, logger, metrics.NoopMetrics, cfg, sync.CLSync) - attributesHandler := attributes.NewAttributesHandler(logger, cfg, engine, l2Source) - syncCfg := &sync.Config{SyncMode: sync.CLSync} - pipeline := derive.NewDerivationPipeline(logger, cfg, l1Source, l1BlobsSource, plasma.Disabled, l2Source, metrics.NoopMetrics) - return &Driver{ - logger: logger, - deriver: &MinimalSyncDeriver{ - logger: logger, - pipeline: pipeline, - attributesHandler: attributesHandler, - l1Source: l1Source, - l2Source: l2Source, - engine: engine, - syncCfg: syncCfg, - cfg: cfg, - }, - l2OutputRoot: l2Source.L2OutputRoot, - targetBlockNum: targetBlockNum, +func (d *Driver) Emit(ev rollup.Event) { + if d.end.Closing() { + return } + d.events = append(d.events, ev) }   -// Step runs the next step of the derivation pipeline. -// Returns nil if there are further steps to be performed -// Returns io.EOF if the derivation completed successfully -// Returns a non-EOF error if the derivation failed -func (d *Driver) Step(ctx context.Context) error { - if err := d.deriver.SyncStep(ctx); errors.Is(err, io.EOF) { - d.logger.Info("Derivation complete: reached L1 head", "head", d.deriver.SafeL2Head()) - return io.EOF - } else if errors.Is(err, derive.NotEnoughData) { - // NotEnoughData is not handled differently than a nil error. - // This used to be returned by the EngineQueue when a block was derived, but also other stages. - // Instead, every driver-loop iteration we check if the target block number has been reached. - d.logger.Debug("Data is lacking") - } else if errors.Is(err, derive.ErrTemporary) { - // While most temporary errors are due to requests for external data failing which can't happen, - // they may also be returned due to other events like channels timing out so need to be handled - d.logger.Warn("Temporary error in derivation", "err", err) - return nil - } else if err != nil { - return fmt.Errorf("pipeline err: %w", err) - } - head := d.deriver.SafeL2Head() - if head.Number >= d.targetBlockNum { - d.logger.Info("Derivation complete: reached L2 block", "head", head) - return io.EOF - } - return nil -} +var ExhaustErr = errors.New("exhausted events before completing program")   -func (d *Driver) SafeHead() eth.L2BlockRef { - return d.deriver.SafeL2Head() -} +func (d *Driver) RunComplete() error { + // Initial reset + d.Emit(engine.ResetEngineRequestEvent{})   -func (d *Driver) ValidateClaim(l2ClaimBlockNum uint64, claimedOutputRoot eth.Bytes32) error { - l2Head := d.SafeHead() - outputRoot, err := d.l2OutputRoot(min(l2ClaimBlockNum, l2Head.Number)) - if err != nil { - return fmt.Errorf("calculate L2 output root: %w", err) - } - d.logger.Info("Validating claim", "head", l2Head, "output", outputRoot, "claim", claimedOutputRoot) - if claimedOutputRoot != outputRoot { - return fmt.Errorf("%w: claim: %v actual: %v", ErrClaimNotValid, claimedOutputRoot, outputRoot) + for !d.end.Closing() { + if len(d.events) == 0 { + return ExhaustErr + } + if len(d.events) > 10000 { // sanity check, in case of bugs. Better than going OOM. + return errors.New("way too many events queued up, something is wrong") + } + ev := d.events[0] + d.events = d.events[1:] + d.deriver.OnEvent(ev) } - return nil + return d.end.Result() }
diff --git OP/op-program/client/driver/driver_test.go CELO/op-program/client/driver/driver_test.go index d61484020289c23cf0cce83a9602e18c3a28e9cb..9ad06e7233fa8e024a6590d9e7525939090e8b4e 100644 --- OP/op-program/client/driver/driver_test.go +++ CELO/op-program/client/driver/driver_test.go @@ -1,156 +1,110 @@ package driver   import ( - "context" "errors" - "fmt" - "io" "testing"   - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" -)   -func TestDerivationComplete(t *testing.T) { - driver := createDriver(t, fmt.Errorf("derivation complete: %w", io.EOF)) - err := driver.Step(context.Background()) - require.ErrorIs(t, err, io.EOF) -} + "github.com/ethereum/go-ethereum/log"   -func TestTemporaryError(t *testing.T) { - driver := createDriver(t, fmt.Errorf("whoopsie: %w", derive.ErrTemporary)) - err := driver.Step(context.Background()) - require.NoError(t, err, "should allow derivation to continue after temporary error") -} + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/testlog" +)   -func TestNotEnoughDataError(t *testing.T) { - driver := createDriver(t, fmt.Errorf("idk: %w", derive.NotEnoughData)) - err := driver.Step(context.Background()) - require.NoError(t, err) +type fakeEnd struct { + closing bool + result error }   -func TestGenericError(t *testing.T) { - expected := errors.New("boom") - driver := createDriver(t, expected) - err := driver.Step(context.Background()) - require.ErrorIs(t, err, expected) +func (d *fakeEnd) Closing() bool { + return d.closing }   -func TestTargetBlock(t *testing.T) { - t.Run("Reached", func(t *testing.T) { - driver := createDriverWithNextBlock(t, derive.NotEnoughData, 1000) - driver.targetBlockNum = 1000 - err := driver.Step(context.Background()) - require.ErrorIs(t, err, io.EOF) - }) - - t.Run("Exceeded", func(t *testing.T) { - driver := createDriverWithNextBlock(t, derive.NotEnoughData, 1000) - driver.targetBlockNum = 500 - err := driver.Step(context.Background()) - require.ErrorIs(t, err, io.EOF) - }) - - t.Run("NotYetReached", func(t *testing.T) { - driver := createDriverWithNextBlock(t, derive.NotEnoughData, 1000) - driver.targetBlockNum = 1001 - err := driver.Step(context.Background()) - // No error to indicate derivation should continue - require.NoError(t, err) - }) -} - -func TestNoError(t *testing.T) { - driver := createDriver(t, nil) - err := driver.Step(context.Background()) - require.NoError(t, err) +func (d *fakeEnd) Result() error { + return d.result }   -func TestValidateClaim(t *testing.T) { - t.Run("Valid", func(t *testing.T) { - driver := createDriver(t, io.EOF) - expected := eth.Bytes32{0x11} - driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) { - return expected, nil +func TestDriver(t *testing.T) { + newTestDriver := func(t *testing.T, onEvent func(d *Driver, end *fakeEnd, ev rollup.Event)) *Driver { + logger := testlog.Logger(t, log.LevelInfo) + end := &fakeEnd{} + d := &Driver{ + logger: logger, + end: end, } - err := driver.ValidateClaim(uint64(0), expected) - require.NoError(t, err) - }) + d.deriver = rollup.DeriverFunc(func(ev rollup.Event) { + onEvent(d, end, ev) + }) + return d + }   - t.Run("Valid-PriorToSafeHead", func(t *testing.T) { - driver := createDriverWithNextBlock(t, io.EOF, 10) - expected := eth.Bytes32{0x11} - requestedOutputRoot := uint64(0) - driver.l2OutputRoot = func(blockNum uint64) (eth.Bytes32, error) { - requestedOutputRoot = blockNum - return expected, nil - } - err := driver.ValidateClaim(uint64(20), expected) - require.NoError(t, err) - require.Equal(t, uint64(10), requestedOutputRoot) + t.Run("insta complete", func(t *testing.T) { + d := newTestDriver(t, func(d *Driver, end *fakeEnd, ev rollup.Event) { + end.closing = true + }) + require.NoError(t, d.RunComplete()) })   - t.Run("Invalid", func(t *testing.T) { - driver := createDriver(t, io.EOF) - driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) { - return eth.Bytes32{0x22}, nil - } - err := driver.ValidateClaim(uint64(0), eth.Bytes32{0x11}) - require.ErrorIs(t, err, ErrClaimNotValid) + t.Run("insta error", func(t *testing.T) { + mockErr := errors.New("mock error") + d := newTestDriver(t, func(d *Driver, end *fakeEnd, ev rollup.Event) { + end.closing = true + end.result = mockErr + }) + require.ErrorIs(t, mockErr, d.RunComplete()) })   - t.Run("Invalid-PriorToSafeHead", func(t *testing.T) { - driver := createDriverWithNextBlock(t, io.EOF, 10) - expected := eth.Bytes32{0x11} - requestedOutputRoot := uint64(0) - driver.l2OutputRoot = func(blockNum uint64) (eth.Bytes32, error) { - requestedOutputRoot = blockNum - return expected, nil - } - err := driver.ValidateClaim(uint64(20), eth.Bytes32{0x55}) - require.ErrorIs(t, err, ErrClaimNotValid) - require.Equal(t, uint64(10), requestedOutputRoot) + t.Run("success after a few events", func(t *testing.T) { + count := 0 + d := newTestDriver(t, func(d *Driver, end *fakeEnd, ev rollup.Event) { + if count > 3 { + end.closing = true + return + } + count += 1 + d.Emit(TestEvent{}) + }) + require.NoError(t, d.RunComplete()) })   - t.Run("Error", func(t *testing.T) { - driver := createDriver(t, io.EOF) - expectedErr := errors.New("boom") - driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) { - return eth.Bytes32{}, expectedErr - } - err := driver.ValidateClaim(uint64(0), eth.Bytes32{0x11}) - require.ErrorIs(t, err, expectedErr) + t.Run("error after a few events", func(t *testing.T) { + count := 0 + mockErr := errors.New("mock error") + d := newTestDriver(t, func(d *Driver, end *fakeEnd, ev rollup.Event) { + if count > 3 { + end.closing = true + end.result = mockErr + return + } + count += 1 + d.Emit(TestEvent{}) + }) + require.ErrorIs(t, mockErr, d.RunComplete()) }) -} - -func createDriver(t *testing.T, derivationResult error) *Driver { - return createDriverWithNextBlock(t, derivationResult, 0) -}   -func createDriverWithNextBlock(t *testing.T, derivationResult error, nextBlockNum uint64) *Driver { - derivation := &stubDeriver{nextErr: derivationResult, nextBlockNum: nextBlockNum} - return &Driver{ - logger: testlog.Logger(t, log.LevelDebug), - deriver: derivation, - l2OutputRoot: nil, - targetBlockNum: 1_000_000, - } -} - -type stubDeriver struct { - nextErr error - nextBlockNum uint64 -} - -func (s *stubDeriver) SyncStep(ctx context.Context) error { - return s.nextErr -} + t.Run("exhaust events", func(t *testing.T) { + count := 0 + d := newTestDriver(t, func(d *Driver, end *fakeEnd, ev rollup.Event) { + if count < 3 { // stop generating events after a while, without changing end condition + d.Emit(TestEvent{}) + } + count += 1 + }) + require.ErrorIs(t, ExhaustErr, d.RunComplete()) + })   -func (s *stubDeriver) SafeL2Head() eth.L2BlockRef { - return eth.L2BlockRef{ - Number: s.nextBlockNum, - } + t.Run("queued events", func(t *testing.T) { + count := 0 + d := newTestDriver(t, func(d *Driver, end *fakeEnd, ev rollup.Event) { + if count < 3 { + d.Emit(TestEvent{}) + d.Emit(TestEvent{}) + } + count += 1 + }) + require.ErrorIs(t, ExhaustErr, d.RunComplete()) + // add 1 for initial event that RunComplete fires + require.Equal(t, 1+3*2, count, "must have queued up 2 events 3 times") + }) }
diff --git OP/op-program/client/driver/program.go CELO/op-program/client/driver/program.go new file mode 100644 index 0000000000000000000000000000000000000000..9ed08e531d916f015777114d6065844516ee81fd --- /dev/null +++ CELO/op-program/client/driver/program.go @@ -0,0 +1,88 @@ +package driver + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/rollup/engine" +) + +// ProgramDeriver expresses how engine and derivation events are +// translated and monitored to execute the pure L1 to L2 state transition. +// +// The ProgramDeriver stops at the target block number or with an error result. +type ProgramDeriver struct { + logger log.Logger + + Emitter rollup.EventEmitter + + closing bool + result error + targetBlockNum uint64 +} + +func (d *ProgramDeriver) Closing() bool { + return d.closing +} + +func (d *ProgramDeriver) Result() error { + return d.result +} + +func (d *ProgramDeriver) OnEvent(ev rollup.Event) { + switch x := ev.(type) { + case engine.EngineResetConfirmedEvent: + d.Emitter.Emit(derive.ConfirmPipelineResetEvent{}) + // After initial reset we can request the pending-safe block, + // where attributes will be generated on top of. + d.Emitter.Emit(engine.PendingSafeRequestEvent{}) + case engine.PendingSafeUpdateEvent: + d.Emitter.Emit(derive.PipelineStepEvent{PendingSafe: x.PendingSafe}) + case derive.DeriverMoreEvent: + d.Emitter.Emit(engine.PendingSafeRequestEvent{}) + case derive.DerivedAttributesEvent: + // Allow new attributes to be generated. + // We will process the current attributes synchronously, + // triggering a single PendingSafeUpdateEvent or InvalidPayloadAttributesEvent, + // to continue derivation from. + d.Emitter.Emit(derive.ConfirmReceivedAttributesEvent{}) + // No need to queue the attributes, since there is no unsafe chain to consolidate against, + // and no temporary-error retry to perform on block processing. + d.Emitter.Emit(engine.ProcessAttributesEvent{Attributes: x.Attributes}) + case engine.InvalidPayloadAttributesEvent: + // If a set of attributes was invalid, then we drop the attributes, + // and continue with the next. + d.Emitter.Emit(engine.PendingSafeRequestEvent{}) + case engine.ForkchoiceUpdateEvent: + if x.SafeL2Head.Number >= d.targetBlockNum { + d.logger.Info("Derivation complete: reached L2 block", "head", x.SafeL2Head) + d.closing = true + } + case derive.DeriverIdleEvent: + // Not enough data to reach target + d.closing = true + d.logger.Info("Derivation complete: no further data to process") + case rollup.ResetEvent: + d.closing = true + d.result = fmt.Errorf("unexpected reset error: %w", x.Err) + case rollup.L1TemporaryErrorEvent: + d.closing = true + d.result = fmt.Errorf("unexpected L1 error: %w", x.Err) + case rollup.EngineTemporaryErrorEvent: + // (Legacy case): While most temporary errors are due to requests for external data failing which can't happen, + // they may also be returned due to other events like channels timing out so need to be handled + d.logger.Warn("Temporary error in derivation", "err", x.Err) + d.Emitter.Emit(engine.PendingSafeRequestEvent{}) + case rollup.CriticalErrorEvent: + d.closing = true + d.result = x.Err + default: + // Other events can be ignored safely. + // They are broadcast, but only consumed by the other derivers, + // or do not affect the state-transition. + return + } +}
diff --git OP/op-program/client/driver/program_test.go CELO/op-program/client/driver/program_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bca2ecaf0b7299076dc4d151013ce99f348c7bef --- /dev/null +++ CELO/op-program/client/driver/program_test.go @@ -0,0 +1,161 @@ +package driver + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/rollup/engine" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/testutils" +) + +func TestProgramDeriver(t *testing.T) { + newProgram := func(t *testing.T, target uint64) (*ProgramDeriver, *testutils.MockEmitter) { + m := &testutils.MockEmitter{} + logger := testlog.Logger(t, log.LevelInfo) + prog := &ProgramDeriver{ + logger: logger, + Emitter: m, + targetBlockNum: target, + } + return prog, m + } + // step 0 assumption: engine performs reset upon ResetEngineRequestEvent. + // step 1: engine completes reset + t.Run("engine reset confirmed", func(t *testing.T) { + p, m := newProgram(t, 1000) + m.ExpectOnce(derive.ConfirmPipelineResetEvent{}) + m.ExpectOnce(engine.PendingSafeRequestEvent{}) + p.OnEvent(engine.EngineResetConfirmedEvent{}) + m.AssertExpectations(t) + require.False(t, p.closing) + require.NoError(t, p.result) + require.False(t, p.closing) + require.NoError(t, p.result) + }) + // step 2: more derivation work, triggered when pending safe data is published + t.Run("pending safe update", func(t *testing.T) { + p, m := newProgram(t, 1000) + ref := eth.L2BlockRef{Number: 123} + m.ExpectOnce(derive.PipelineStepEvent{PendingSafe: ref}) + p.OnEvent(engine.PendingSafeUpdateEvent{PendingSafe: ref}) + m.AssertExpectations(t) + require.False(t, p.closing) + require.NoError(t, p.result) + }) + // step 3: if no attributes are generated, loop back to derive more. + t.Run("deriver more", func(t *testing.T) { + p, m := newProgram(t, 1000) + m.ExpectOnce(engine.PendingSafeRequestEvent{}) + p.OnEvent(derive.DeriverMoreEvent{}) + m.AssertExpectations(t) + require.False(t, p.closing) + require.NoError(t, p.result) + }) + // step 4: if attributes are derived, pass them to the engine. + t.Run("derived attributes", func(t *testing.T) { + p, m := newProgram(t, 1000) + attrib := &derive.AttributesWithParent{Parent: eth.L2BlockRef{Number: 123}} + m.ExpectOnce(derive.ConfirmReceivedAttributesEvent{}) + m.ExpectOnce(engine.ProcessAttributesEvent{Attributes: attrib}) + p.OnEvent(derive.DerivedAttributesEvent{Attributes: attrib}) + m.AssertExpectations(t) + require.False(t, p.closing) + require.NoError(t, p.result) + }) + // step 5: if attributes were invalid, continue with derivation for new attributes. + t.Run("invalid payload", func(t *testing.T) { + p, m := newProgram(t, 1000) + m.ExpectOnce(engine.PendingSafeRequestEvent{}) + p.OnEvent(engine.InvalidPayloadAttributesEvent{Attributes: &derive.AttributesWithParent{}}) + m.AssertExpectations(t) + require.False(t, p.closing) + require.NoError(t, p.result) + }) + // step 6: if attributes were valid, we may have reached the target. + // Or back to step 2 (PendingSafeUpdateEvent) + t.Run("forkchoice update", func(t *testing.T) { + t.Run("surpassed", func(t *testing.T) { + p, m := newProgram(t, 42) + p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42 + 1}}) + m.AssertExpectations(t) + require.True(t, p.closing) + require.NoError(t, p.result) + }) + t.Run("completed", func(t *testing.T) { + p, m := newProgram(t, 42) + p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42}}) + m.AssertExpectations(t) + require.True(t, p.closing) + require.NoError(t, p.result) + }) + t.Run("incomplete", func(t *testing.T) { + p, m := newProgram(t, 42) + p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42 - 1}}) + m.AssertExpectations(t) + require.False(t, p.closing) + require.NoError(t, p.result) + }) + }) + // on exhaustion of input data: stop without error + t.Run("deriver idle", func(t *testing.T) { + p, m := newProgram(t, 1000) + p.OnEvent(derive.DeriverIdleEvent{}) + m.AssertExpectations(t) + require.True(t, p.closing) + require.Nil(t, p.result) + }) + // on inconsistent chain data: stop with error + t.Run("reset event", func(t *testing.T) { + p, m := newProgram(t, 1000) + p.OnEvent(rollup.ResetEvent{Err: errors.New("reset test err")}) + m.AssertExpectations(t) + require.True(t, p.closing) + require.NotNil(t, p.result) + }) + // on L1 temporary error: stop with error + t.Run("L1 temporary error event", func(t *testing.T) { + p, m := newProgram(t, 1000) + p.OnEvent(rollup.L1TemporaryErrorEvent{Err: errors.New("temp test err")}) + m.AssertExpectations(t) + require.True(t, p.closing) + require.NotNil(t, p.result) + }) + // on engine temporary error: continue derivation (because legacy, not all connection related) + t.Run("engine temp error event", func(t *testing.T) { + p, m := newProgram(t, 1000) + m.ExpectOnce(engine.PendingSafeRequestEvent{}) + p.OnEvent(rollup.EngineTemporaryErrorEvent{Err: errors.New("temp test err")}) + m.AssertExpectations(t) + require.False(t, p.closing) + require.NoError(t, p.result) + }) + // on critical error: stop + t.Run("critical error event", func(t *testing.T) { + p, m := newProgram(t, 1000) + p.OnEvent(rollup.ResetEvent{Err: errors.New("crit test err")}) + m.AssertExpectations(t) + require.True(t, p.closing) + require.NotNil(t, p.result) + }) + t.Run("unknown event", func(t *testing.T) { + p, m := newProgram(t, 1000) + p.OnEvent(TestEvent{}) + m.AssertExpectations(t) + require.False(t, p.closing) + require.NoError(t, p.result) + }) +} + +type TestEvent struct{} + +func (ev TestEvent) String() string { + return "test-event" +}
diff --git OP/op-program/client/program.go CELO/op-program/client/program.go index a471941f2005399ad35c142ebe87777d1798be6d..bee3892fca16d1536e882eb34ac1d26de0d9e5d4 100644 --- OP/op-program/client/program.go +++ CELO/op-program/client/program.go @@ -1,7 +1,6 @@ package client   import ( - "context" "errors" "fmt" "io" @@ -13,6 +12,7 @@ "github.com/ethereum/go-ethereum/params"   "github.com/ethereum-optimism/optimism/op-node/rollup" preimage "github.com/ethereum-optimism/optimism/op-preimage" + "github.com/ethereum-optimism/optimism/op-program/client/claim" cldr "github.com/ethereum-optimism/optimism/op-program/client/driver" "github.com/ethereum-optimism/optimism/op-program/client/l1" "github.com/ethereum-optimism/optimism/op-program/client/l2" @@ -26,7 +26,7 @@ func Main(logger log.Logger) { log.Info("Starting fault proof program client") preimageOracle := CreatePreimageChannel() preimageHinter := CreateHinterChannel() - if err := RunProgram(logger, preimageOracle, preimageHinter); errors.Is(err, cldr.ErrClaimNotValid) { + if err := RunProgram(logger, preimageOracle, preimageHinter); errors.Is(err, claim.ErrClaimNotValid) { log.Error("Claim is invalid", "err", err) os.Exit(1) } else if err != nil { @@ -72,14 +72,10 @@ l2Source := l2.NewOracleEngine(cfg, logger, engineBackend)   logger.Info("Starting derivation") d := cldr.NewDriver(logger, cfg, l1Source, l1BlobsSource, l2Source, l2ClaimBlockNum) - for { - if err = d.Step(context.Background()); errors.Is(err, io.EOF) { - break - } else if err != nil { - return err - } + if err := d.RunComplete(); err != nil { + return fmt.Errorf("failed to run program to completion: %w", err) } - return d.ValidateClaim(l2ClaimBlockNum, eth.Bytes32(l2Claim)) + return claim.ValidateClaim(logger, l2ClaimBlockNum, eth.Bytes32(l2Claim), l2Source) }   func CreateHinterChannel() oppio.FileChannel {
diff --git OP/op-proposer/proposer/driver.go CELO/op-proposer/proposer/driver.go index 5160d0aa242b53df2e84ff46b8767f68bf24b769..52122e3cfd4e9c9bec845189e07202eae73686bb 100644 --- OP/op-proposer/proposer/driver.go +++ CELO/op-proposer/proposer/driver.go @@ -223,7 +223,7 @@ Context: cCtx, } nextCheckpointBlock, err := l.l2ooContract.NextBlockNumber(callOpts) if err != nil { - l.Log.Error("proposer unable to get next block number", "err", err) + l.Log.Error("Proposer unable to get next block number", "err", err) return nil, false, err } // Fetch the current L2 heads @@ -234,7 +234,7 @@ }   // Ensure that we do not submit a block in the future if currentBlockNumber.Cmp(nextCheckpointBlock) < 0 { - l.Log.Debug("proposer submission interval has not elapsed", "currentBlockNumber", currentBlockNumber, "nextBlockNumber", nextCheckpointBlock) + l.Log.Debug("Proposer submission interval has not elapsed", "currentBlockNumber", currentBlockNumber, "nextBlockNumber", nextCheckpointBlock) return nil, false, nil }   @@ -246,7 +246,7 @@ // option is set, it will return the safe head block number, and if not, it will return the finalized head block number. func (l *L2OutputSubmitter) FetchCurrentBlockNumber(ctx context.Context) (*big.Int, error) { rollupClient, err := l.RollupProvider.RollupClient(ctx) if err != nil { - l.Log.Error("proposer unable to get rollup client", "err", err) + l.Log.Error("Proposer unable to get rollup client", "err", err) return nil, err }   @@ -255,7 +255,7 @@ defer cancel()   status, err := rollupClient.SyncStatus(cCtx) if err != nil { - l.Log.Error("proposer unable to get sync status", "err", err) + l.Log.Error("Proposer unable to get sync status", "err", err) return nil, err }   @@ -272,7 +272,7 @@ func (l *L2OutputSubmitter) FetchOutput(ctx context.Context, block *big.Int) (*eth.OutputResponse, bool, error) { rollupClient, err := l.RollupProvider.RollupClient(ctx) if err != nil { - l.Log.Error("proposer unable to get rollup client", "err", err) + l.Log.Error("Proposer unable to get rollup client", "err", err) return nil, false, err }   @@ -281,21 +281,21 @@ defer cancel()   output, err := rollupClient.OutputAtBlock(cCtx, block.Uint64()) if err != nil { - l.Log.Error("failed to fetch output at block", "block", block, "err", err) + l.Log.Error("Failed to fetch output at block", "block", block, "err", err) return nil, false, err } if output.Version != supportedL2OutputVersion { - l.Log.Error("unsupported l2 output version", "output_version", output.Version, "supported_version", supportedL2OutputVersion) + l.Log.Error("Unsupported l2 output version", "output_version", output.Version, "supported_version", supportedL2OutputVersion) return nil, false, errors.New("unsupported l2 output version") } if output.BlockRef.Number != block.Uint64() { // sanity check, e.g. in case of bad RPC caching - l.Log.Error("invalid blockNumber", "next_block", block, "output_block", output.BlockRef.Number) + l.Log.Error("Invalid blockNumber", "next_block", block, "output_block", output.BlockRef.Number) return nil, false, errors.New("invalid blockNumber") }   // Always propose if it's part of the Finalized L2 chain. Or if allowed, if it's part of the safe L2 chain. if output.BlockRef.Number > output.Status.FinalizedL2.Number && (!l.Cfg.AllowNonFinalized || output.BlockRef.Number > output.Status.SafeL2.Number) { - l.Log.Debug("not proposing yet, L2 block is not ready for proposal", + l.Log.Debug("Not proposing yet, L2 block is not ready for proposal", "l2_proposal", output.BlockRef, "l2_safe", output.Status.SafeL2, "l2_finalized", output.Status.FinalizedL2, @@ -351,7 +351,7 @@ if err != nil { return err } for l1head <= blockNum { - l.Log.Debug("waiting for l1 head > l1blocknum1+1", "l1head", l1head, "l1blocknum", blockNum) + l.Log.Debug("Waiting for l1 head > l1blocknum1+1", "l1head", l1head, "l1blocknum", blockNum) select { case <-ticker.C: l1head, err = l.Txmgr.BlockNumber(ctx) @@ -372,6 +372,7 @@ if err != nil { return err }   + l.Log.Info("Proposing output root", "output", output.OutputRoot, "block", output.BlockRef) var receipt *types.Receipt if l.Cfg.DisputeGameFactoryAddr != nil { data, bond, err := l.ProposeL2OutputDGFTxData(output) @@ -403,9 +404,9 @@ } }   if receipt.Status == types.ReceiptStatusFailed { - l.Log.Error("proposer tx successfully published but reverted", "tx_hash", receipt.TxHash) + l.Log.Error("Proposer tx successfully published but reverted", "tx_hash", receipt.TxHash) } else { - l.Log.Info("proposer tx successfully published", + l.Log.Info("Proposer tx successfully published", "tx_hash", receipt.TxHash, "l1blocknum", output.Status.CurrentL1.Number, "l1blockhash", output.Status.CurrentL1.Hash)
diff --git OP/op-proposer/proposer/service.go CELO/op-proposer/proposer/service.go index 0e5012d1ab85bd56b936dff424e76c769b1b5f51..f40fdf0b496abc6fd1a749ae74246ff7399dd0b6 100644 --- OP/op-proposer/proposer/service.go +++ CELO/op-proposer/proposer/service.go @@ -186,19 +186,19 @@ }   func (ps *ProposerService) initMetricsServer(cfg *CLIConfig) error { if !cfg.MetricsConfig.Enabled { - ps.Log.Info("metrics disabled") + ps.Log.Info("Metrics disabled") return nil } m, ok := ps.Metrics.(opmetrics.RegistryMetricer) if !ok { return fmt.Errorf("metrics were enabled, but metricer %T does not expose registry for metrics-server", ps.Metrics) } - ps.Log.Debug("starting metrics server", "addr", cfg.MetricsConfig.ListenAddr, "port", cfg.MetricsConfig.ListenPort) + ps.Log.Debug("Starting metrics server", "addr", cfg.MetricsConfig.ListenAddr, "port", cfg.MetricsConfig.ListenPort) metricsSrv, err := opmetrics.StartServer(m.Registry(), cfg.MetricsConfig.ListenAddr, cfg.MetricsConfig.ListenPort) if err != nil { return fmt.Errorf("failed to start metrics server: %w", err) } - ps.Log.Info("started metrics server", "addr", metricsSrv.Addr()) + ps.Log.Info("Started metrics server", "addr", metricsSrv.Addr()) ps.metricsSrv = metricsSrv return nil }
diff --git OP/op-service/errutil/errors.go CELO/op-service/errutil/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..f04ffe205ba150091eaf50591edac8c38575d592 --- /dev/null +++ CELO/op-service/errutil/errors.go @@ -0,0 +1,22 @@ +package errutil + +import ( + "errors" + "fmt" +) + +type errWithData interface { + ErrorData() interface{} +} + +// TryAddRevertReason attempts to extract the revert reason from geth RPC client errors and adds it to the error message. +// This is most useful when attempting to execute gas, as if the transaction reverts this will then show the reason. +func TryAddRevertReason(err error) error { + var errData errWithData + ok := errors.As(err, &errData) + if ok { + return fmt.Errorf("%w, reason: %v", err, errData.ErrorData()) + } else { + return err + } +}
diff --git OP/op-service/errutil/errors_test.go CELO/op-service/errutil/errors_test.go new file mode 100644 index 0000000000000000000000000000000000000000..77f2d1de81130cf80da31fdc2484a3d71d5b06f0 --- /dev/null +++ CELO/op-service/errutil/errors_test.go @@ -0,0 +1,32 @@ +package errutil + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTryAddRevertReason(t *testing.T) { + t.Run("AddsReason", func(t *testing.T) { + err := stubError{} + result := TryAddRevertReason(err) + require.Contains(t, result.Error(), "kaboom") + }) + + t.Run("ReturnOriginalWhenNoErrorDataMethod", func(t *testing.T) { + err := errors.New("boom") + result := TryAddRevertReason(err) + require.Same(t, err, result) + }) +} + +type stubError struct{} + +func (s stubError) Error() string { + return "where's the" +} + +func (s stubError) ErrorData() interface{} { + return "kaboom" +}
diff --git OP/op-service/eth/blobs_api_test.go CELO/op-service/eth/blobs_api_test.go index f7439ff75a9c88d4e2df1604f5e3d530cea2e7c1..7c585cb7ac997c665afdda48ed9c28cb284e46c4 100644 --- OP/op-service/eth/blobs_api_test.go +++ CELO/op-service/eth/blobs_api_test.go @@ -16,7 +16,7 @@ type dataJson struct { Data map[string]any `json:"data"` }   -// TestAPIGenesisResponse tests that json unmarshaling a json response from a +// TestAPIGenesisResponse tests that json unmarshalling a json response from a // eth/v1/beacon/genesis beacon node call into a APIGenesisResponse object // fills all existing fields with the expected values, thereby confirming that // APIGenesisResponse is compatible with the current beacon node API. @@ -41,7 +41,7 @@ require.NoError(err) require.Equal(jsonMap.Data["genesis_time"].(string), string(genesisTime)) }   -// TestAPIConfigResponse tests that json unmarshaling a json response from a +// TestAPIConfigResponse tests that json unmarshalling a json response from a // eth/v1/config/spec beacon node call into a APIConfigResponse object // fills all existing fields with the expected values, thereby confirming that // APIGenesisResponse is compatible with the current beacon node API. @@ -66,7 +66,7 @@ require.NoError(err) require.Equal(jsonMap.Data["SECONDS_PER_SLOT"].(string), string(secPerSlot)) }   -// TestAPIGetBlobSidecarsResponse tests that json unmarshaling a json response from a +// TestAPIGetBlobSidecarsResponse tests that json unmarshalling a json response from a // eth/v1/beacon/blob_sidecars/<X> beacon node call into a APIGetBlobSidecarsResponse object // fills all existing fields with the expected values, thereby confirming that // APIGenesisResponse is compatible with the current beacon node API.
diff --git OP/op-service/metrics/balance.go CELO/op-service/metrics/balance.go index 27131fa5eef0442b9ac07e06c9d30ce04c974b8c..4c11ef99896717eb7f9b3bb55335ed9aa5f0d04a 100644 --- OP/op-service/metrics/balance.go +++ CELO/op-service/metrics/balance.go @@ -19,7 +19,7 @@ // LaunchBalanceMetrics starts a periodic query of the balance of the supplied account and records it // to the "balance" metric of the namespace. The balance of the account is recorded in Ether (not Wei). // Cancel the supplied context to shut down the go routine func LaunchBalanceMetrics(log log.Logger, r *prometheus.Registry, ns string, client *ethclient.Client, account common.Address) *clock.LoopFn { - balanceGuage := promauto.With(r).NewGauge(prometheus.GaugeOpts{ + balanceGauge := promauto.With(r).NewGauge(prometheus.GaugeOpts{ Namespace: ns, Name: "balance", Help: "balance (in ether) of account " + account.String(), @@ -33,7 +33,7 @@ log.Warn("failed to get balance of account", "err", err, "address", account) return } bal := eth.WeiToEther(bigBal) - balanceGuage.Set(bal) + balanceGauge.Set(bal) }, func() error { log.Info("balance metrics shutting down") return nil
diff --git OP/op-service/metrics/cli.go CELO/op-service/metrics/cli.go index ac02e33bf0c6946a0ee63aac9f2d818150192868..dcbe3d0af2edcc092c6711e01488a3399f777c9e 100644 --- OP/op-service/metrics/cli.go +++ CELO/op-service/metrics/cli.go @@ -17,6 +17,8 @@ defaultListenAddr = "0.0.0.0" defaultListenPort = 7300 )   +var ErrInvalidPort = errors.New("invalid metrics port") + func DefaultCLIConfig() CLIConfig { return CLIConfig{ Enabled: false, @@ -59,7 +61,7 @@ return nil }   if m.ListenPort < 0 || m.ListenPort > math.MaxUint16 { - return errors.New("invalid metrics port") + return ErrInvalidPort }   return nil
diff --git OP/op-service/oppprof/cli.go CELO/op-service/oppprof/cli.go index 710cbeaaf7641e58f32e2021dbaac98975e5be32..d6ccb8566960cba5d4558d8effaf4e02db442885 100644 --- OP/op-service/oppprof/cli.go +++ CELO/op-service/oppprof/cli.go @@ -22,6 +22,7 @@ defaultListenAddr = "0.0.0.0" defaultListenPort = 6060 )   +var ErrInvalidPort = errors.New("invalid pprof port") var allowedProfileTypes = []profileType{"cpu", "heap", "goroutine", "threadcreate", "block", "mutex", "allocs"}   type profileType string @@ -122,7 +123,7 @@ return nil }   if m.ListenPort < 0 || m.ListenPort > math.MaxUint16 { - return errors.New("invalid pprof port") + return ErrInvalidPort }   return nil
diff --git OP/op-service/predeploys/addresses.go CELO/op-service/predeploys/addresses.go index 3602bb5448d3f79114baef4506265ae87a34df7f..33426b78f96d11221570b9628bcafcefa78d8071 100644 --- OP/op-service/predeploys/addresses.go +++ CELO/op-service/predeploys/addresses.go @@ -38,6 +38,16 @@ SenderCreator_v060 = "0x7fc98430eaedbb6070b35b39d798725049088348" EntryPoint_v060 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" SenderCreator_v070 = "0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C" EntryPoint_v070 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + + // Celo + CeloRegistry = "0x000000000000000000000000000000000000ce10" + GoldToken = "0x471ece3750da237f93b8e339c536989b8978a438" + FeeHandler = "0xcd437749e43a154c07f3553504c68fbfd56b8778" + MentoFeeHandlerSeller = "0x4efa274b7e33476c961065000d58ee09f7921a74" + UniswapFeeHandlerSeller = "0xd3aee28548dbb65df03981f0dc0713bfcbd10a97" + SortedOracles = "0xefb84935239dacdecf7c5ba76d8de40b077b7b33" + AddressSortedLinkedListWithMedian = "0xED477A99035d0c1e11369F1D7A4e587893cc002B" + FeeCurrency = "0x4200000000000000000000000000000000001022" )   var ( @@ -76,6 +86,18 @@ EntryPoint_v070Addr = common.HexToAddress(EntryPoint_v070)   Predeploys = make(map[string]*Predeploy) PredeploysByAddress = make(map[common.Address]*Predeploy) + + // Celo + CeloRegistryAddr = common.HexToAddress(CeloRegistry) + GoldTokenAddr = common.HexToAddress(GoldToken) + FeeHandlerAddr = common.HexToAddress(FeeHandler) + MentoFeeHandlerSellerAddr = common.HexToAddress(MentoFeeHandlerSeller) + UniswapFeeHandlerSellerAddr = common.HexToAddress(UniswapFeeHandlerSeller) + SortedOraclesAddr = common.HexToAddress(SortedOracles) + AddressSortedLinkedListWithMedianAddr = common.HexToAddress(AddressSortedLinkedListWithMedian) + FeeCurrencyAddr = common.HexToAddress(FeeCurrency) + + CeloPredeploys = make(map[string]*Predeploy) )   func init() { @@ -155,6 +177,19 @@ } Predeploys["EntryPoint_v070"] = &Predeploy{ Address: EntryPoint_v070Addr, ProxyDisabled: true, + } + + // Celo + CeloPredeploys["CeloRegistry"] = &Predeploy{Address: CeloRegistryAddr} + CeloPredeploys["GoldToken"] = &Predeploy{Address: GoldTokenAddr} + CeloPredeploys["FeeHandler"] = &Predeploy{Address: FeeHandlerAddr} + CeloPredeploys["MentoFeeHandlerSeller"] = &Predeploy{Address: MentoFeeHandlerSellerAddr} + CeloPredeploys["UniswapFeeHandlerSeller"] = &Predeploy{Address: UniswapFeeHandlerSellerAddr} + CeloPredeploys["SortedOracles"] = &Predeploy{Address: SortedOraclesAddr} + CeloPredeploys["AddressSortedLinkedListWithMedian"] = &Predeploy{Address: AddressSortedLinkedListWithMedianAddr} + CeloPredeploys["FeeCurrency"] = &Predeploy{Address: FeeCurrencyAddr} + for key, predeploy := range CeloPredeploys { + Predeploys[key] = predeploy }   for _, predeploy := range Predeploys {
diff --git OP/op-service/rethdb-reader/Cargo.lock CELO/op-service/rethdb-reader/Cargo.lock index 5b4c003d5b0c01c5cdd4778209878c0a5841242b..0b66063c3372b4df7728c8e213dc6a3a5c971345 100644 --- OP/op-service/rethdb-reader/Cargo.lock +++ CELO/op-service/rethdb-reader/Cargo.lock @@ -1020,16 +1020,15 @@ ]   [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rustc_version 0.4.0", "subtle", "zeroize", @@ -2649,12 +2648,6 @@ name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "platforms" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"   [[package]] name = "polyval"
diff --git OP/op-service/rpc/cli.go CELO/op-service/rpc/cli.go index 866dfd0336d72f1dd2f8161e4b96d234fdd318bb..99cb2dd6c9b225559b8bbbba18abf99089e2bbf6 100644 --- OP/op-service/rpc/cli.go +++ CELO/op-service/rpc/cli.go @@ -14,6 +14,8 @@ PortFlagName = "rpc.port" EnableAdminFlagName = "rpc.enable-admin" )   +var ErrInvalidPort = errors.New("invalid RPC port") + func CLIFlags(envPrefix string) []cli.Flag { return []cli.Flag{ &cli.StringFlag{ @@ -52,7 +54,7 @@ }   func (c CLIConfig) Check() error { if c.ListenPort < 0 || c.ListenPort > math.MaxUint16 { - return errors.New("invalid RPC port") + return ErrInvalidPort }   return nil
diff --git OP/op-service/safego/nocopy.go CELO/op-service/safego/nocopy.go new file mode 100644 index 0000000000000000000000000000000000000000..1b7e6e89f9cc8520565a6f54cab602b9406fed02 --- /dev/null +++ CELO/op-service/safego/nocopy.go @@ -0,0 +1,29 @@ +package safego + +// NoCopy is a super simple safety util taken from the Go atomic lib. +// +// NoCopy may be added to structs which must not be copied +// after the first use. +// +// The NoCopy struct is empty, so should be a zero-cost util at runtime. +// +// See https://golang.org/issues/8005#issuecomment-190753527 +// for details. +// +// Note that it must not be embedded, due to the Lock and Unlock methods. +// +// Like: +// ``` +// +// type Example { +// V uint64 +// _ NoCopy +// } +// +// Then run: `go vet -copylocks .` +// ``` +type NoCopy struct{} + +// Lock is a no-op used by -copylocks checker from `go vet`. +func (*NoCopy) Lock() {} +func (*NoCopy) Unlock() {}
diff --git OP/op-service/sources/rollupclient.go CELO/op-service/sources/rollupclient.go index 96cd166732a9686e7f875f451b6510a29513d67a..14c38d35b4e8e5f96161edd73e7163f3d0d6b479 100644 --- OP/op-service/sources/rollupclient.go +++ CELO/op-service/sources/rollupclient.go @@ -3,10 +3,9 @@ import ( "context"   - "golang.org/x/exp/slog" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "golang.org/x/exp/slog"   "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/client" @@ -69,6 +68,10 @@ }   func (r *RollupClient) PostUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error { return r.rpc.CallContext(ctx, nil, "admin_postUnsafePayload", payload) +} + +func (r *RollupClient) OverrideLeader(ctx context.Context) error { + return r.rpc.CallContext(ctx, nil, "admin_overrideLeader") }   func (r *RollupClient) SetLogLevel(ctx context.Context, lvl slog.Level) error {
diff --git OP/op-service/testutils/metrics.go CELO/op-service/testutils/metrics.go index 79e575cab1e246181d3c492a6ceb687e315dd08d..19e2baf85f25df00e55333c28b0fe75bfcde4cc7 100644 --- OP/op-service/testutils/metrics.go +++ CELO/op-service/testutils/metrics.go @@ -69,3 +69,6 @@ func (n *TestRPCMetrics) RecordRPCClientResponse(method string, err error) {}   func (t *TestDerivationMetrics) SetDerivationIdle(idle bool) {} + +func (t *TestDerivationMetrics) RecordPipelineReset() { +}
diff --git OP/op-service/testutils/mock_emitter.go CELO/op-service/testutils/mock_emitter.go new file mode 100644 index 0000000000000000000000000000000000000000..e9b502dfee38e8783574fb8e8089fa956ef0c49d --- /dev/null +++ CELO/op-service/testutils/mock_emitter.go @@ -0,0 +1,41 @@ +package testutils + +import ( + "github.com/stretchr/testify/mock" + + "github.com/ethereum-optimism/optimism/op-node/rollup" +) + +type MockEmitter struct { + mock.Mock +} + +func (m *MockEmitter) Emit(ev rollup.Event) { + m.Mock.MethodCalled("Emit", ev) +} + +func (m *MockEmitter) ExpectOnce(expected rollup.Event) { + m.Mock.On("Emit", expected).Once() +} + +func (m *MockEmitter) ExpectMaybeRun(fn func(ev rollup.Event)) { + m.Mock.On("Emit", mock.Anything).Maybe().Run(func(args mock.Arguments) { + fn(args.Get(0).(rollup.Event)) + }) +} + +func (m *MockEmitter) ExpectOnceType(typ string) { + m.Mock.On("Emit", mock.AnythingOfType(typ)).Once() +} + +func (m *MockEmitter) ExpectOnceRun(fn func(ev rollup.Event)) { + m.Mock.On("Emit", mock.Anything).Once().Run(func(args mock.Arguments) { + fn(args.Get(0).(rollup.Event)) + }) +} + +func (m *MockEmitter) AssertExpectations(t mock.TestingT) { + m.Mock.AssertExpectations(t) +} + +var _ rollup.EventEmitter = (*MockEmitter)(nil)
diff --git OP/op-service/txmgr/send_state.go CELO/op-service/txmgr/send_state.go index 4066a0caa1499386659f7e0530158292a39d4828..6053d3f3c5e39c80cd7a05fe3d9543714dc863fa 100644 --- OP/op-service/txmgr/send_state.go +++ CELO/op-service/txmgr/send_state.go @@ -16,7 +16,7 @@ // declaration once op-geth is updated to this version. ErrAlreadyReserved = errors.New("address already reserved")   // Returned by CriticalError when the system is unable to get the tx into the mempool in the - // alloted time + // allotted time ErrMempoolDeadlineExpired = errors.New("failed to get tx into the mempool") )   @@ -125,7 +125,7 @@ case s.nonceTooLowCount >= s.safeAbortNonceTooLowCount: // we have exceeded the nonce too low count return core.ErrNonceTooLow case s.successFullPublishCount == 0 && s.now().After(s.txInMempoolDeadline): - // unable to get the tx into the mempool in the alloted time + // unable to get the tx into the mempool in the allotted time return ErrMempoolDeadlineExpired case s.alreadyReserved: // incompatible tx type in mempool
diff --git OP/op-service/txmgr/txmgr.go CELO/op-service/txmgr/txmgr.go index 015d6edc767f34abfa620a358185a1f92d58c416..2e5a87d073da35dc7b2657077b0a4a0c5ee2da02 100644 --- OP/op-service/txmgr/txmgr.go +++ CELO/op-service/txmgr/txmgr.go @@ -10,6 +10,7 @@ "sync" "sync/atomic" "time"   + "github.com/ethereum-optimism/optimism/op-service/errutil" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" @@ -272,7 +273,7 @@ Data: candidate.TxData, Value: candidate.Value, }) if err != nil { - return nil, fmt.Errorf("failed to estimate gas: %w", err) + return nil, fmt.Errorf("failed to estimate gas: %w", errutil.TryAddRevertReason(err)) } gasLimit = gas }
diff --git OP/op-supervisor/.gitignore CELO/op-supervisor/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ba077a4031add5b3a04384f8b9cfc414efbf47dd --- /dev/null +++ CELO/op-supervisor/.gitignore @@ -0,0 +1 @@ +bin
diff --git OP/op-supervisor/Makefile CELO/op-supervisor/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..1f58b6f02384365cfa7429c8355c6da984e9cae8 --- /dev/null +++ CELO/op-supervisor/Makefile @@ -0,0 +1,24 @@ +GITCOMMIT ?= $(shell git rev-parse HEAD) +GITDATE ?= $(shell git show -s --format='%ct') +VERSION ?= v0.0.0 + +LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) +LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) +LDFLAGSSTRING +=-X main.Version=$(VERSION) +LDFLAGSSTRING +=-X main.Meta=$(VERSION_META) +LDFLAGS := -ldflags "$(LDFLAGSSTRING)" + + +op-supervisor: + env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/op-supervisor ./cmd + +clean: + rm bin/op-supervisor + +test: + go test -v ./... + +.PHONY: \ + op-supervisor \ + clean \ + test
diff --git OP/op-supervisor/cmd/main.go CELO/op-supervisor/cmd/main.go new file mode 100644 index 0000000000000000000000000000000000000000..01444e01b92578b3579c1cbefae10c4a93d5c399 --- /dev/null +++ CELO/op-supervisor/cmd/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "os" + + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/urfave/cli/v2" + + "github.com/ethereum/go-ethereum/log" + + opservice "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/cliapp" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-service/metrics/doc" + "github.com/ethereum-optimism/optimism/op-service/opio" + "github.com/ethereum-optimism/optimism/op-supervisor/flags" + "github.com/ethereum-optimism/optimism/op-supervisor/metrics" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor" +) + +var ( + Version = "v0.0.1" + GitCommit = "" + GitDate = "" +) + +func main() { + ctx := opio.WithInterruptBlocker(context.Background()) + err := run(ctx, os.Args, fromConfig) + if err != nil { + log.Crit("Application failed", "message", err) + } +} + +func run(ctx context.Context, args []string, fn supervisor.MainFn) error { + oplog.SetupDefaults() + + app := cli.NewApp() + app.Flags = cliapp.ProtectFlags(flags.Flags) + app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "") + app.Name = "op-supervisor" + app.Usage = "op-supervisor monitors cross-L2 interop messaging" + app.Description = "The op-supervisor monitors cross-L2 interop messaging by pre-fetching events and then resolving the cross-L2 dependencies to answer safety queries." + app.Action = cliapp.LifecycleCmd(supervisor.Main(app.Version, fn)) + app.Commands = []*cli.Command{ + { + Name: "doc", + Subcommands: doc.NewSubcommands(metrics.NewMetrics("default")), + }, + } + return app.RunContext(ctx, args) +} + +func fromConfig(ctx context.Context, cfg *config.Config, logger log.Logger) (cliapp.Lifecycle, error) { + return supervisor.SupervisorFromConfig(ctx, cfg, logger) +}
diff --git OP/op-supervisor/cmd/main_test.go CELO/op-supervisor/cmd/main_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6a463a81275aaef7f6644b80f8b42766c87a845f --- /dev/null +++ CELO/op-supervisor/cmd/main_test.go @@ -0,0 +1,130 @@ +package main + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/cliapp" +) + +var ( + ValidL2RPCs = []string{"http;//localhost:8545"} + ValidDatadir = "./supervisor_test_datadir" +) + +func TestLogLevel(t *testing.T) { + t.Run("RejectInvalid", func(t *testing.T) { + verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs("--log.level=foo")) + }) + + for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} { + lvl := lvl + t.Run("AcceptValid_"+lvl, func(t *testing.T) { + logger, _, err := dryRunWithArgs(addRequiredArgs("--log.level", lvl)) + require.NoError(t, err) + require.NotNil(t, logger) + }) + } +} + +func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { + cfg := configForArgs(t, addRequiredArgs()) + defaultCfgTempl := config.NewConfig(ValidL2RPCs, ValidDatadir) + defaultCfg := *defaultCfgTempl + defaultCfg.Version = Version + require.Equal(t, defaultCfg, *cfg) +} + +func TestL2RPCs(t *testing.T) { + t.Run("Required", func(t *testing.T) { + verifyArgsInvalid(t, "flag l2-rpcs is required", addRequiredArgsExcept("--l2-rpcs")) + }) + + t.Run("Valid", func(t *testing.T) { + url1 := "http://example.com:1234" + url2 := "http://foobar.com:1234" + cfg := configForArgs(t, addRequiredArgsExcept("--l2-rpcs", "--l2-rpcs="+url1+","+url2)) + require.Equal(t, []string{url1, url2}, cfg.L2RPCs) + }) +} + +func TestDatadir(t *testing.T) { + t.Run("Required", func(t *testing.T) { + verifyArgsInvalid(t, "flag datadir is required", addRequiredArgsExcept("--datadir")) + }) + + t.Run("Valid", func(t *testing.T) { + dir := "foo" + cfg := configForArgs(t, addRequiredArgsExcept("--datadir", "--datadir", dir)) + require.Equal(t, dir, cfg.Datadir) + }) +} + +func TestMockRun(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgs("--mock-run")) + require.Equal(t, true, cfg.MockRun) + }) +} + +func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) { + _, _, err := dryRunWithArgs(cliArgs) + require.ErrorContains(t, err, messageContains) +} + +func configForArgs(t *testing.T, cliArgs []string) *config.Config { + _, cfg, err := dryRunWithArgs(cliArgs) + require.NoError(t, err) + return cfg +} + +func dryRunWithArgs(cliArgs []string) (log.Logger, *config.Config, error) { + cfg := new(config.Config) + var logger log.Logger + fullArgs := append([]string{"op-supervisor"}, cliArgs...) + testErr := errors.New("dry-run") + err := run(context.Background(), fullArgs, func(ctx context.Context, config *config.Config, log log.Logger) (cliapp.Lifecycle, error) { + logger = log + cfg = config + return nil, testErr + }) + if errors.Is(err, testErr) { // expected error + err = nil + } + return logger, cfg, err +} + +func addRequiredArgs(args ...string) []string { + req := requiredArgs() + combined := toArgList(req) + return append(combined, args...) +} + +func addRequiredArgsExcept(name string, optionalArgs ...string) []string { + req := requiredArgs() + delete(req, name) + return append(toArgList(req), optionalArgs...) +} + +func toArgList(req map[string]string) []string { + var combined []string + for name, value := range req { + combined = append(combined, fmt.Sprintf("%s=%s", name, value)) + } + return combined +} + +func requiredArgs() map[string]string { + args := map[string]string{ + "--l2-rpcs": ValidL2RPCs[0], + "--datadir": ValidDatadir, + } + return args +}
diff --git OP/op-supervisor/config/config.go CELO/op-supervisor/config/config.go new file mode 100644 index 0000000000000000000000000000000000000000..dbf723d55d3b4e1033258a1280d817ec50d71863 --- /dev/null +++ CELO/op-supervisor/config/config.go @@ -0,0 +1,58 @@ +package config + +import ( + "errors" + + oplog "github.com/ethereum-optimism/optimism/op-service/log" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" +) + +var ( + ErrMissingL2RPC = errors.New("must specify at least one L2 RPC") + ErrMissingDatadir = errors.New("must specify datadir") +) + +type Config struct { + Version string + + LogConfig oplog.CLIConfig + MetricsConfig opmetrics.CLIConfig + PprofConfig oppprof.CLIConfig + RPC oprpc.CLIConfig + + // MockRun runs the service with a mock backend + MockRun bool + + L2RPCs []string + Datadir string +} + +func (c *Config) Check() error { + var result error + result = errors.Join(result, c.MetricsConfig.Check()) + result = errors.Join(result, c.PprofConfig.Check()) + result = errors.Join(result, c.RPC.Check()) + if len(c.L2RPCs) == 0 { + result = errors.Join(result, ErrMissingL2RPC) + } + if c.Datadir == "" { + result = errors.Join(result, ErrMissingDatadir) + } + return result +} + +// NewConfig creates a new config using default values whenever possible. +// Required options with no suitable default are passed as parameters. +func NewConfig(l2RPCs []string, datadir string) *Config { + return &Config{ + LogConfig: oplog.DefaultCLIConfig(), + MetricsConfig: opmetrics.DefaultCLIConfig(), + PprofConfig: oppprof.DefaultCLIConfig(), + RPC: oprpc.DefaultCLIConfig(), + MockRun: false, + L2RPCs: l2RPCs, + Datadir: datadir, + } +}
diff --git OP/op-supervisor/config/config_test.go CELO/op-supervisor/config/config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ef84e4be81bd7d18993a59fadbbe14fd55b23918 --- /dev/null +++ CELO/op-supervisor/config/config_test.go @@ -0,0 +1,52 @@ +package config + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/stretchr/testify/require" +) + +func TestDefaultConfigIsValid(t *testing.T) { + cfg := validConfig() + require.NoError(t, cfg.Check()) +} + +func TestRequireL2RPC(t *testing.T) { + cfg := validConfig() + cfg.L2RPCs = []string{} + require.ErrorIs(t, cfg.Check(), ErrMissingL2RPC) +} + +func TestRequireDatadir(t *testing.T) { + cfg := validConfig() + cfg.Datadir = "" + require.ErrorIs(t, cfg.Check(), ErrMissingDatadir) +} + +func TestValidateMetricsConfig(t *testing.T) { + cfg := validConfig() + cfg.MetricsConfig.Enabled = true + cfg.MetricsConfig.ListenPort = -1 + require.ErrorIs(t, cfg.Check(), metrics.ErrInvalidPort) +} + +func TestValidatePprofConfig(t *testing.T) { + cfg := validConfig() + cfg.PprofConfig.ListenEnabled = true + cfg.PprofConfig.ListenPort = -1 + require.ErrorIs(t, cfg.Check(), oppprof.ErrInvalidPort) +} + +func TestValidateRPCConfig(t *testing.T) { + cfg := validConfig() + cfg.RPC.ListenPort = -1 + require.ErrorIs(t, cfg.Check(), rpc.ErrInvalidPort) +} + +func validConfig() *Config { + // Should be valid using only the required arguments passed in via the constructor. + return NewConfig([]string{"http://localhost:8545"}, "./supervisor_config_testdir") +}
diff --git OP/op-supervisor/flags/flags.go CELO/op-supervisor/flags/flags.go new file mode 100644 index 0000000000000000000000000000000000000000..1759381694acb2da176f63ecda219404dff1a934 --- /dev/null +++ CELO/op-supervisor/flags/flags.go @@ -0,0 +1,83 @@ +package flags + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/urfave/cli/v2" + + opservice "github.com/ethereum-optimism/optimism/op-service" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" +) + +const EnvVarPrefix = "OP_SUPERVISOR" + +func prefixEnvVars(name string) []string { + return opservice.PrefixEnvVar(EnvVarPrefix, name) +} + +var ( + L2RPCsFlag = &cli.StringSliceFlag{ + Name: "l2-rpcs", + Usage: "L2 RPC sources.", + EnvVars: prefixEnvVars("L2_RPCS"), + } + DataDirFlag = &cli.PathFlag{ + Name: "datadir", + Usage: "Directory to store data generated as part of responding to games", + EnvVars: prefixEnvVars("DATADIR"), + } + MockRunFlag = &cli.BoolFlag{ + Name: "mock-run", + Usage: "Mock run, no actual backend used, just presenting the service", + EnvVars: prefixEnvVars("MOCK_RUN"), + Hidden: true, // this is for testing only + } +) + +var requiredFlags = []cli.Flag{ + L2RPCsFlag, + DataDirFlag, +} + +var optionalFlags = []cli.Flag{ + MockRunFlag, +} + +func init() { + optionalFlags = append(optionalFlags, oprpc.CLIFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...) + + Flags = append(Flags, requiredFlags...) + Flags = append(Flags, optionalFlags...) +} + +// Flags contains the list of configuration options available to the binary. +var Flags []cli.Flag + +func CheckRequired(ctx *cli.Context) error { + for _, f := range requiredFlags { + if !ctx.IsSet(f.Names()[0]) { + return fmt.Errorf("flag %s is required", f.Names()[0]) + } + } + return nil +} + +func ConfigFromCLI(ctx *cli.Context, version string) *config.Config { + return &config.Config{ + Version: version, + LogConfig: oplog.ReadCLIConfig(ctx), + MetricsConfig: opmetrics.ReadCLIConfig(ctx), + PprofConfig: oppprof.ReadCLIConfig(ctx), + RPC: oprpc.ReadCLIConfig(ctx), + MockRun: ctx.Bool(MockRunFlag.Name), + L2RPCs: ctx.StringSlice(L2RPCsFlag.Name), + Datadir: ctx.Path(DataDirFlag.Name), + } +}
diff --git OP/op-supervisor/flags/flags_test.go CELO/op-supervisor/flags/flags_test.go new file mode 100644 index 0000000000000000000000000000000000000000..504d9c8644dafd67fb9fb34edea75141820e9650 --- /dev/null +++ CELO/op-supervisor/flags/flags_test.go @@ -0,0 +1,89 @@ +package flags + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" + + opservice "github.com/ethereum-optimism/optimism/op-service" +) + +// TestOptionalFlagsDontSetRequired asserts that all flags deemed optional set +// the Required field to false. +func TestOptionalFlagsDontSetRequired(t *testing.T) { + for _, flag := range optionalFlags { + reqFlag, ok := flag.(cli.RequiredFlag) + require.True(t, ok) + require.False(t, reqFlag.IsRequired()) + } +} + +// TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags. +func TestUniqueFlags(t *testing.T) { + seenCLI := make(map[string]struct{}) + for _, flag := range Flags { + for _, name := range flag.Names() { + if _, ok := seenCLI[name]; ok { + t.Errorf("duplicate flag %s", name) + continue + } + seenCLI[name] = struct{}{} + } + } +} + +// TestBetaFlags test that all flags starting with "beta." have "BETA_" in the env var, and vice versa. +func TestBetaFlags(t *testing.T) { + for _, flag := range Flags { + envFlag, ok := flag.(interface { + GetEnvVars() []string + }) + if !ok || len(envFlag.GetEnvVars()) == 0 { // skip flags without env-var support + continue + } + name := flag.Names()[0] + envName := envFlag.GetEnvVars()[0] + if strings.HasPrefix(name, "beta.") { + require.Contains(t, envName, "BETA_", "%q flag must contain BETA in env var to match \"beta.\" flag name", name) + } + if strings.Contains(envName, "BETA_") { + require.True(t, strings.HasPrefix(name, "beta."), "%q flag must start with \"beta.\" in flag name to match \"BETA_\" env var", name) + } + } +} + +func TestHasEnvVar(t *testing.T) { + for _, flag := range Flags { + flag := flag + flagName := flag.Names()[0] + + t.Run(flagName, func(t *testing.T) { + envFlagGetter, ok := flag.(interface { + GetEnvVars() []string + }) + envFlags := envFlagGetter.GetEnvVars() + require.True(t, ok, "must be able to cast the flag to an EnvVar interface") + require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") + }) + } +} + +func TestEnvVarFormat(t *testing.T) { + for _, flag := range Flags { + flag := flag + flagName := flag.Names()[0] + + t.Run(flagName, func(t *testing.T) { + envFlagGetter, ok := flag.(interface { + GetEnvVars() []string + }) + envFlags := envFlagGetter.GetEnvVars() + require.True(t, ok, "must be able to cast the flag to an EnvVar interface") + require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") + expectedEnvVar := opservice.FlagNameToEnvVarName(flagName, "OP_SUPERVISOR") + require.Equal(t, expectedEnvVar, envFlags[0]) + }) + } +}
diff --git OP/op-supervisor/metrics/metrics.go CELO/op-supervisor/metrics/metrics.go new file mode 100644 index 0000000000000000000000000000000000000000..b659e71b95fa266372437c07b83e8f305e4aa8f3 --- /dev/null +++ CELO/op-supervisor/metrics/metrics.go @@ -0,0 +1,84 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" +) + +const Namespace = "op_supervisor" + +type Metricer interface { + RecordInfo(version string) + RecordUp() + + opmetrics.RPCMetricer + + Document() []opmetrics.DocumentedMetric +} + +type Metrics struct { + ns string + registry *prometheus.Registry + factory opmetrics.Factory + + opmetrics.RPCMetrics + + info prometheus.GaugeVec + up prometheus.Gauge +} + +var _ Metricer = (*Metrics)(nil) + +// implements the Registry getter, for metrics HTTP server to hook into +var _ opmetrics.RegistryMetricer = (*Metrics)(nil) + +func NewMetrics(procName string) *Metrics { + if procName == "" { + procName = "default" + } + ns := Namespace + "_" + procName + + registry := opmetrics.NewRegistry() + factory := opmetrics.With(registry) + + return &Metrics{ + ns: ns, + registry: registry, + factory: factory, + + RPCMetrics: opmetrics.MakeRPCMetrics(ns, factory), + + info: *factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "info", + Help: "Pseudo-metric tracking version and config info", + }, []string{ + "version", + }), + up: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "up", + Help: "1 if the op-supervisor has finished starting up", + }), + } +} + +func (m *Metrics) Registry() *prometheus.Registry { + return m.registry +} + +func (m *Metrics) Document() []opmetrics.DocumentedMetric { + return m.factory.Document() +} + +// RecordInfo sets a pseudo-metric that contains versioning and config info for the op-supervisor. +func (m *Metrics) RecordInfo(version string) { + m.info.WithLabelValues(version).Set(1) +} + +// RecordUp sets the up metric to 1. +func (m *Metrics) RecordUp() { + prometheus.MustRegister() + m.up.Set(1) +}
diff --git OP/op-supervisor/metrics/noop.go CELO/op-supervisor/metrics/noop.go new file mode 100644 index 0000000000000000000000000000000000000000..f87e75a1116ca5765feb946ce02ebf51ef0b8eef --- /dev/null +++ CELO/op-supervisor/metrics/noop.go @@ -0,0 +1,16 @@ +package metrics + +import ( + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" +) + +type noopMetrics struct { + opmetrics.NoopRPCMetrics +} + +var NoopMetrics Metricer = new(noopMetrics) + +func (*noopMetrics) Document() []opmetrics.DocumentedMetric { return nil } + +func (*noopMetrics) RecordInfo(version string) {} +func (*noopMetrics) RecordUp() {}
diff --git OP/op-supervisor/supervisor/backend/backend.go CELO/op-supervisor/supervisor/backend/backend.go new file mode 100644 index 0000000000000000000000000000000000000000..074d498f4e5230d98e26c6a33aeec38f9600de70 --- /dev/null +++ CELO/op-supervisor/supervisor/backend/backend.go @@ -0,0 +1,60 @@ +package backend + +import ( + "context" + "errors" + "io" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type SupervisorBackend struct { + started atomic.Bool + + // TODO(protocol-quest#287): collection of logdbs per chain + // TODO(protocol-quest#288): collection of logdb updating services per chain +} + +var _ frontend.Backend = (*SupervisorBackend)(nil) + +var _ io.Closer = (*SupervisorBackend)(nil) + +func NewSupervisorBackend() *SupervisorBackend { + return &SupervisorBackend{} +} + +func (su *SupervisorBackend) Start(ctx context.Context) error { + if !su.started.CompareAndSwap(false, true) { + return errors.New("already started") + } + // TODO(protocol-quest#288): start logdb updating services of all chains + return nil +} + +func (su *SupervisorBackend) Stop(ctx context.Context) error { + if !su.started.CompareAndSwap(true, false) { + return errors.New("already stopped") + } + // TODO(protocol-quest#288): stop logdb updating services of all chains + return nil +} + +func (su *SupervisorBackend) Close() error { + // TODO(protocol-quest#288): close logdb of all chains + return nil +} + +func (su *SupervisorBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) { + // TODO(protocol-quest#288): hook up to logdb lookup + return types.CrossUnsafe, nil +} + +func (su *SupervisorBackend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) { + // TODO(protocol-quest#288): hook up to logdb lookup + return types.CrossUnsafe, nil +}
diff --git OP/op-supervisor/supervisor/backend/config.go CELO/op-supervisor/supervisor/backend/config.go new file mode 100644 index 0000000000000000000000000000000000000000..efe2f4463c911234b1b9c6b452130b4daf7b91ac --- /dev/null +++ CELO/op-supervisor/supervisor/backend/config.go @@ -0,0 +1,5 @@ +package backend + +type Config struct { + // TODO(protocol-quest#288): configure list of chains and their RPC endpoints / potential alternative data sources +}
diff --git OP/op-supervisor/supervisor/backend/db/db.go CELO/op-supervisor/supervisor/backend/db/db.go new file mode 100644 index 0000000000000000000000000000000000000000..7d83ef4750c85b3efb8adb209bcb5b61459da516 --- /dev/null +++ CELO/op-supervisor/supervisor/backend/db/db.go @@ -0,0 +1,486 @@ +package db + +import ( + "errors" + "fmt" + "io" + "math" + "sync" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" + "github.com/ethereum/go-ethereum/log" +) + +const ( + searchCheckpointFrequency = 256 + + eventFlagIncrementLogIdx = byte(1) + eventFlagHasExecutingMessage = byte(1) << 1 +) + +const ( + typeSearchCheckpoint byte = iota + typeCanonicalHash + typeInitiatingEvent + typeExecutingLink + typeExecutingCheck +) + +type Metrics interface { + RecordEntryCount(count int64) + RecordSearchEntriesRead(count int64) +} + +type logContext struct { + blockNum uint64 + logIdx uint32 +} + +type EntryStore interface { + Size() int64 + Read(idx int64) (entrydb.Entry, error) + Append(entries ...entrydb.Entry) error + Truncate(idx int64) error + Close() error +} + +// DB implements an append only database for log data and cross-chain dependencies. +// +// To keep the append-only format, reduce data size, and support reorg detection and registering of executing-messages: +// +// Use a fixed 24 bytes per entry. +// +// Data is an append-only log, that can be binary searched for any necessary event data. +// +// Rules: +// if entry_index % 256 == 0: must be type 0. For easy binary search. +// type 1 always adjacent to type 0 +// type 2 "diff" values are offsets from type 0 values (always within 256 entries range) +// type 3 always after type 2 +// type 4 always after type 3 +// +// Types (<type> = 1 byte): +// type 0: "search checkpoint" <type><uint64 block number: 8 bytes><uint32 event index offset: 4 bytes><uint64 timestamp: 8 bytes> = 20 bytes +// type 1: "canonical hash" <type><parent blockhash truncated: 20 bytes> = 21 bytes +// type 2: "initiating event" <type><blocknum diff: 1 byte><event flags: 1 byte><event-hash: 20 bytes> = 23 bytes +// type 3: "executing link" <type><chain: 4 bytes><blocknum: 8 bytes><event index: 3 bytes><uint64 timestamp: 8 bytes> = 24 bytes +// type 4: "executing check" <type><event-hash: 20 bytes> = 21 bytes +// other types: future compat. E.g. for linking to L1, registering block-headers as a kind of initiating-event, tracking safe-head progression, etc. +// +// Right-pad each entry that is not 24 bytes. +// +// event-flags: each bit represents a boolean value, currently only two are defined +// * event-flags & 0x01 - true if the log index should increment. Should only be false when the event is immediately after a search checkpoint and canonical hash +// * event-flags & 0x02 - true if the initiating event has an executing link that should follow. Allows detecting when the executing link failed to write. +// event-hash: H(origin, timestamp, payloadhash); enough to check identifier matches & payload matches. +type DB struct { + log log.Logger + m Metrics + store EntryStore + rwLock sync.RWMutex + + lastEntryContext logContext +} + +func NewFromFile(logger log.Logger, m Metrics, path string) (*DB, error) { + store, err := entrydb.NewEntryDB(logger, path) + if err != nil { + return nil, fmt.Errorf("failed to open DB: %w", err) + } + return NewFromEntryStore(logger, m, store) +} + +func NewFromEntryStore(logger log.Logger, m Metrics, store EntryStore) (*DB, error) { + db := &DB{ + log: logger, + m: m, + store: store, + } + if err := db.init(); err != nil { + return nil, fmt.Errorf("failed to init database: %w", err) + } + return db, nil +} + +func (db *DB) lastEntryIdx() int64 { + return db.store.Size() - 1 +} + +func (db *DB) init() error { + defer db.updateEntryCountMetric() // Always update the entry count metric after init completes + if err := db.trimInvalidTrailingEntries(); err != nil { + return fmt.Errorf("failed to trim invalid trailing entries: %w", err) + } + if db.lastEntryIdx() < 0 { + // Database is empty so no context to load + return nil + } + + lastCheckpoint := (db.lastEntryIdx() / searchCheckpointFrequency) * searchCheckpointFrequency + i, err := db.newIterator(lastCheckpoint) + if err != nil { + return fmt.Errorf("failed to create iterator at last search checkpoint: %w", err) + } + // Read all entries until the end of the file + for { + _, _, _, err := i.NextLog() + if errors.Is(err, io.EOF) { + break + } else if err != nil { + return fmt.Errorf("failed to init from existing entries: %w", err) + } + } + db.lastEntryContext = i.current + return nil +} + +func (db *DB) trimInvalidTrailingEntries() error { + i := db.lastEntryIdx() + for ; i >= 0; i-- { + entry, err := db.store.Read(i) + if err != nil { + return fmt.Errorf("failed to read %v to check for trailing entries: %w", i, err) + } + if entry[0] == typeExecutingCheck { + // executing check is a valid final entry + break + } + if entry[0] == typeInitiatingEvent { + evt, err := newInitiatingEventFromEntry(entry) + if err != nil { + // Entry is invalid, keep walking backwards + continue + } + if !evt.hasExecMsg { + // init event with no exec msg is a valid final entry + break + } + } + } + if i < db.lastEntryIdx() { + db.log.Warn("Truncating unexpected trailing entries", "prev", db.lastEntryIdx(), "new", i) + return db.store.Truncate(i) + } + return nil +} + +func (db *DB) updateEntryCountMetric() { + db.m.RecordEntryCount(db.lastEntryIdx() + 1) +} + +// ClosestBlockInfo returns the block number and hash of the highest recorded block at or before blockNum. +// Since block data is only recorded in search checkpoints, this may return an earlier block even if log data is +// recorded for the requested block. +func (db *DB) ClosestBlockInfo(blockNum uint64) (uint64, TruncatedHash, error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + checkpointIdx, err := db.searchCheckpoint(blockNum, math.MaxUint32) + if err != nil { + return 0, TruncatedHash{}, fmt.Errorf("no checkpoint at or before block %v found: %w", blockNum, err) + } + checkpoint, err := db.readSearchCheckpoint(checkpointIdx) + if err != nil { + return 0, TruncatedHash{}, fmt.Errorf("failed to reach checkpoint: %w", err) + } + entry, err := db.readCanonicalHash(checkpointIdx + 1) + if err != nil { + return 0, TruncatedHash{}, fmt.Errorf("failed to read canonical hash: %w", err) + } + return checkpoint.blockNum, entry.hash, nil +} + +// Contains return true iff the specified logHash is recorded in the specified blockNum and logIdx. +// logIdx is the index of the log in the array of all logs the block. +// This can be used to check the validity of cross-chain interop events. +func (db *DB) Contains(blockNum uint64, logIdx uint32, logHash TruncatedHash) (bool, error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + db.log.Trace("Checking for log", "blockNum", blockNum, "logIdx", logIdx, "hash", logHash) + + evtHash, _, err := db.findLogInfo(blockNum, logIdx) + if errors.Is(err, ErrNotFound) { + // Did not find a log at blockNum and logIdx + return false, nil + } else if err != nil { + return false, err + } + db.log.Trace("Found initiatingEvent", "blockNum", blockNum, "logIdx", logIdx, "hash", evtHash) + // Found the requested block and log index, check if the hash matches + return evtHash == logHash, nil +} + +// Executes checks if the log identified by the specific block number and log index, has an ExecutingMessage associated +// with it that needs to be checked as part of interop validation. +// logIdx is the index of the log in the array of all logs the block. +// Returns the ExecutingMessage if it exists, or ExecutingMessage{} if the log is found but has no ExecutingMessage. +// Returns ErrNotFound if the specified log does not exist in the database. +func (db *DB) Executes(blockNum uint64, logIdx uint32) (ExecutingMessage, error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + _, iter, err := db.findLogInfo(blockNum, logIdx) + if err != nil { + return ExecutingMessage{}, err + } + execMsg, err := iter.ExecMessage() + if err != nil { + return ExecutingMessage{}, fmt.Errorf("failed to read executing message: %w", err) + } + return execMsg, nil +} + +func (db *DB) findLogInfo(blockNum uint64, logIdx uint32) (TruncatedHash, *iterator, error) { + entryIdx, err := db.searchCheckpoint(blockNum, logIdx) + if errors.Is(err, io.EOF) { + // Did not find a checkpoint to start reading from so the log cannot be present. + return TruncatedHash{}, nil, ErrNotFound + } else if err != nil { + return TruncatedHash{}, nil, err + } + + i, err := db.newIterator(entryIdx) + if err != nil { + return TruncatedHash{}, nil, fmt.Errorf("failed to create iterator: %w", err) + } + db.log.Trace("Starting search", "entry", entryIdx, "blockNum", i.current.blockNum, "logIdx", i.current.logIdx) + defer func() { + db.m.RecordSearchEntriesRead(i.entriesRead) + }() + for { + evtBlockNum, evtLogIdx, evtHash, err := i.NextLog() + if errors.Is(err, io.EOF) { + // Reached end of log without finding the event + return TruncatedHash{}, nil, ErrNotFound + } else if err != nil { + return TruncatedHash{}, nil, fmt.Errorf("failed to read next log: %w", err) + } + if evtBlockNum == blockNum && evtLogIdx == logIdx { + db.log.Trace("Found initiatingEvent", "blockNum", evtBlockNum, "logIdx", evtLogIdx, "hash", evtHash) + return evtHash, i, nil + } + if evtBlockNum > blockNum || (evtBlockNum == blockNum && evtLogIdx > logIdx) { + // Progressed past the requested log without finding it. + return TruncatedHash{}, nil, ErrNotFound + } + } +} + +func (db *DB) newIterator(startCheckpointEntry int64) (*iterator, error) { + checkpoint, err := db.readSearchCheckpoint(startCheckpointEntry) + if err != nil { + return nil, fmt.Errorf("failed to read search checkpoint entry %v: %w", startCheckpointEntry, err) + } + startIdx := startCheckpointEntry + 2 + firstEntry, err := db.store.Read(startIdx) + if errors.Is(err, io.EOF) { + // There should always be an entry after a checkpoint and canonical hash so an EOF here is data corruption + return nil, fmt.Errorf("%w: no entry after checkpoint and canonical hash at %v", ErrDataCorruption, startCheckpointEntry) + } else if err != nil { + return nil, fmt.Errorf("failed to read first entry to iterate %v: %w", startCheckpointEntry+2, err) + } + startLogCtx := logContext{ + blockNum: checkpoint.blockNum, + logIdx: checkpoint.logIdx, + } + // Handle starting from a checkpoint after initiating-event but before its executing-link or executing-check + if firstEntry[0] == typeExecutingLink || firstEntry[0] == typeExecutingCheck { + if firstEntry[0] == typeExecutingLink { + // The start checkpoint was between the initiating event and the executing link + // Step back to read the initiating event. The checkpoint block data will be for the initiating event + startIdx = startCheckpointEntry - 1 + } else { + // The start checkpoint was between the executing link and the executing check + // Step back to read the initiating event. The checkpoint block data will be for the initiating event + startIdx = startCheckpointEntry - 2 + } + initEntry, err := db.store.Read(startIdx) + if err != nil { + return nil, fmt.Errorf("failed to read prior initiating event: %w", err) + } + initEvt, err := newInitiatingEventFromEntry(initEntry) + if err != nil { + return nil, fmt.Errorf("invalid initiating event at idx %v: %w", startIdx, err) + } + startLogCtx = initEvt.preContext(startLogCtx) + } + i := &iterator{ + db: db, + // +2 to skip the initial search checkpoint and the canonical hash event after it + nextEntryIdx: startIdx, + current: startLogCtx, + } + return i, nil +} + +// searchCheckpoint performs a binary search of the searchCheckpoint entries to find the closest one at or before +// the requested log. +// Returns the index of the searchCheckpoint to begin reading from or an error +func (db *DB) searchCheckpoint(blockNum uint64, logIdx uint32) (int64, error) { + n := (db.lastEntryIdx() / searchCheckpointFrequency) + 1 + // Define x[-1] < target and x[n] >= target. + // Invariant: x[i-1] < target, x[j] >= target. + i, j := int64(0), n + for i < j { + h := int64(uint64(i+j) >> 1) // avoid overflow when computing h + checkpoint, err := db.readSearchCheckpoint(h * searchCheckpointFrequency) + if err != nil { + return 0, fmt.Errorf("failed to read entry %v: %w", h, err) + } + // i ≤ h < j + if checkpoint.blockNum < blockNum || (checkpoint.blockNum == blockNum && checkpoint.logIdx < logIdx) { + i = h + 1 // preserves x[i-1] < target + } else { + j = h // preserves x[j] >= target + } + } + if i < n { + checkpoint, err := db.readSearchCheckpoint(i * searchCheckpointFrequency) + if err != nil { + return 0, fmt.Errorf("failed to read entry %v: %w", i, err) + } + if checkpoint.blockNum == blockNum && checkpoint.logIdx == logIdx { + // Found entry at requested block number and log index + return i * searchCheckpointFrequency, nil + } + } + if i == 0 { + // There are no checkpoints before the requested blocks + return 0, io.EOF + } + // Not found, need to start reading from the entry prior + return (i - 1) * searchCheckpointFrequency, nil +} + +func (db *DB) AddLog(logHash TruncatedHash, block eth.BlockID, timestamp uint64, logIdx uint32, execMsg *ExecutingMessage) error { + db.rwLock.Lock() + defer db.rwLock.Unlock() + postState := logContext{ + blockNum: block.Number, + logIdx: logIdx, + } + if block.Number == 0 { + return fmt.Errorf("%w: should not have logs in block 0", ErrLogOutOfOrder) + } + if db.lastEntryContext.blockNum > block.Number { + return fmt.Errorf("%w: adding block %v, head block: %v", ErrLogOutOfOrder, block.Number, db.lastEntryContext.blockNum) + } + if db.lastEntryContext.blockNum == block.Number && db.lastEntryContext.logIdx+1 != logIdx { + return fmt.Errorf("%w: adding log %v in block %v, but currently at log %v", ErrLogOutOfOrder, logIdx, block.Number, db.lastEntryContext.logIdx) + } + if db.lastEntryContext.blockNum < block.Number && logIdx != 0 { + return fmt.Errorf("%w: adding log %v as first log in block %v", ErrLogOutOfOrder, logIdx, block.Number) + } + var entriesToAdd []entrydb.Entry + newContext := db.lastEntryContext + lastEntryIdx := db.lastEntryIdx() + + addEntry := func(entry entrydb.Entry) { + entriesToAdd = append(entriesToAdd, entry) + lastEntryIdx++ + } + maybeAddCheckpoint := func() { + if (lastEntryIdx+1)%searchCheckpointFrequency == 0 { + addEntry(newSearchCheckpoint(block.Number, logIdx, timestamp).encode()) + addEntry(newCanonicalHash(TruncateHash(block.Hash)).encode()) + newContext = postState + } + } + maybeAddCheckpoint() + + evt, err := newInitiatingEvent(newContext, postState.blockNum, postState.logIdx, logHash, execMsg != nil) + if err != nil { + return fmt.Errorf("failed to create initiating event: %w", err) + } + addEntry(evt.encode()) + + if execMsg != nil { + maybeAddCheckpoint() + link, err := newExecutingLink(*execMsg) + if err != nil { + return fmt.Errorf("failed to create executing link: %w", err) + } + addEntry(link.encode()) + + maybeAddCheckpoint() + addEntry(newExecutingCheck(execMsg.Hash).encode()) + } + if err := db.store.Append(entriesToAdd...); err != nil { + return fmt.Errorf("failed to append entries: %w", err) + } + db.lastEntryContext = postState + db.updateEntryCountMetric() + return nil +} + +// Rewind the database to remove any blocks after headBlockNum +// The block at headBlockNum itself is not removed. +func (db *DB) Rewind(headBlockNum uint64) error { + db.rwLock.Lock() + defer db.rwLock.Unlock() + if headBlockNum >= db.lastEntryContext.blockNum { + // Nothing to do + return nil + } + // Find the last checkpoint before the block to remove + idx, err := db.searchCheckpoint(headBlockNum+1, 0) + if errors.Is(err, io.EOF) { + // Requested a block prior to the first checkpoint + // Delete everything without scanning forward + idx = -1 + } else if err != nil { + return fmt.Errorf("failed to find checkpoint prior to block %v: %w", headBlockNum, err) + } else { + // Scan forward from the checkpoint to find the first entry about a block after headBlockNum + i, err := db.newIterator(idx) + if err != nil { + return fmt.Errorf("failed to create iterator when searching for rewind point: %w", err) + } + // If we don't find any useful logs after the checkpoint, we should delete the checkpoint itself + // So move our delete marker back to include it as a starting point + idx-- + for { + blockNum, _, _, err := i.NextLog() + if errors.Is(err, io.EOF) { + // Reached end of file, we need to keep everything + return nil + } else if err != nil { + return fmt.Errorf("failed to find rewind point: %w", err) + } + if blockNum > headBlockNum { + // Found the first entry we don't need, so stop searching and delete everything after idx + break + } + // Otherwise we need all of the entries the iterator just read + idx = i.nextEntryIdx - 1 + } + } + // Truncate to contain idx+1 entries, since indices are 0 based, this deletes everything after idx + if err := db.store.Truncate(idx); err != nil { + return fmt.Errorf("failed to truncate to block %v: %w", headBlockNum, err) + } + // Use db.init() to find the log context for the new latest log entry + if err := db.init(); err != nil { + return fmt.Errorf("failed to find new last entry context: %w", err) + } + return nil +} + +func (db *DB) readSearchCheckpoint(entryIdx int64) (searchCheckpoint, error) { + data, err := db.store.Read(entryIdx) + if err != nil { + return searchCheckpoint{}, fmt.Errorf("failed to read entry %v: %w", entryIdx, err) + } + return newSearchCheckpointFromEntry(data) +} + +func (db *DB) readCanonicalHash(entryIdx int64) (canonicalHash, error) { + data, err := db.store.Read(entryIdx) + if err != nil { + return canonicalHash{}, fmt.Errorf("failed to read entry %v: %w", entryIdx, err) + } + return newCanonicalHashFromEntry(data) +} + +func (db *DB) Close() error { + return db.store.Close() +}
diff --git OP/op-supervisor/supervisor/backend/db/db_invariants_test.go CELO/op-supervisor/supervisor/backend/db/db_invariants_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8570c530279d17ac899d5178f5462d5b2aad135d --- /dev/null +++ CELO/op-supervisor/supervisor/backend/db/db_invariants_test.go @@ -0,0 +1,260 @@ +package db + +import ( + "errors" + "fmt" + "io" + "os" + "testing" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" + "github.com/stretchr/testify/require" +) + +type statInvariant func(stat os.FileInfo, m *stubMetrics) error +type entryInvariant func(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error + +// checkDBInvariants reads the database log directly and asserts a set of invariants on the data. +func checkDBInvariants(t *testing.T, dbPath string, m *stubMetrics) { + stat, err := os.Stat(dbPath) + require.NoError(t, err) + + statInvariants := []statInvariant{ + invariantFileSizeMultipleOfEntrySize, + invariantFileSizeMatchesEntryCountMetric, + } + for _, invariant := range statInvariants { + require.NoError(t, invariant(stat, m)) + } + + // Read all entries as binary blobs + file, err := os.OpenFile(dbPath, os.O_RDONLY, 0o644) + require.NoError(t, err) + entries := make([]entrydb.Entry, stat.Size()/entrydb.EntrySize) + for i := range entries { + n, err := io.ReadFull(file, entries[i][:]) + require.NoErrorf(t, err, "failed to read entry %v", i) + require.EqualValuesf(t, entrydb.EntrySize, n, "read wrong length for entry %v", i) + } + + entryInvariants := []entryInvariant{ + invariantSearchCheckpointOnlyAtFrequency, + invariantSearchCheckpointAtEverySearchCheckpointFrequency, + invariantCanonicalHashAfterEverySearchCheckpoint, + invariantSearchCheckpointBeforeEveryCanonicalHash, + invariantIncrementLogIdxIfNotImmediatelyAfterCanonicalHash, + invariantExecLinkAfterInitEventWithFlagSet, + invariantExecLinkOnlyAfterInitiatingEventWithFlagSet, + invariantExecCheckAfterExecLink, + invariantExecCheckOnlyAfterExecLink, + invariantValidLastEntry, + } + for i, entry := range entries { + for _, invariant := range entryInvariants { + err := invariant(i, entry, entries, m) + if err != nil { + require.NoErrorf(t, err, "Invariant breached: \n%v", fmtEntries(entries)) + } + } + } +} + +func fmtEntries(entries []entrydb.Entry) string { + out := "" + for i, entry := range entries { + out += fmt.Sprintf("%v: %x\n", i, entry) + } + return out +} + +func invariantFileSizeMultipleOfEntrySize(stat os.FileInfo, _ *stubMetrics) error { + size := stat.Size() + if size%entrydb.EntrySize != 0 { + return fmt.Errorf("expected file size to be a multiple of entry size (%v) but was %v", entrydb.EntrySize, size) + } + return nil +} + +func invariantFileSizeMatchesEntryCountMetric(stat os.FileInfo, m *stubMetrics) error { + size := stat.Size() + if m.entryCount*entrydb.EntrySize != size { + return fmt.Errorf("expected file size to be entryCount (%v) * entrySize (%v) = %v but was %v", m.entryCount, entrydb.EntrySize, m.entryCount*entrydb.EntrySize, size) + } + return nil +} + +func invariantSearchCheckpointOnlyAtFrequency(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entry[0] != typeSearchCheckpoint { + return nil + } + if entryIdx%searchCheckpointFrequency != 0 { + return fmt.Errorf("should only have search checkpoints every %v entries but found at entry %v", searchCheckpointFrequency, entryIdx) + } + return nil +} + +func invariantSearchCheckpointAtEverySearchCheckpointFrequency(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entryIdx%searchCheckpointFrequency == 0 && entry[0] != typeSearchCheckpoint { + return fmt.Errorf("should have search checkpoints every %v entries but entry %v was %x", searchCheckpointFrequency, entryIdx, entry) + } + return nil +} + +func invariantCanonicalHashAfterEverySearchCheckpoint(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entry[0] != typeSearchCheckpoint { + return nil + } + if entryIdx+1 >= len(entries) { + return fmt.Errorf("expected canonical hash after search checkpoint at entry %v but no further entries found", entryIdx) + } + nextEntry := entries[entryIdx+1] + if nextEntry[0] != typeCanonicalHash { + return fmt.Errorf("expected canonical hash after search checkpoint at entry %v but got %x", entryIdx, nextEntry) + } + return nil +} + +// invariantSearchCheckpointBeforeEveryCanonicalHash ensures we don't have extra canonical-hash entries +func invariantSearchCheckpointBeforeEveryCanonicalHash(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entry[0] != typeCanonicalHash { + return nil + } + if entryIdx == 0 { + return fmt.Errorf("expected search checkpoint before canonical hash at entry %v but no previous entries present", entryIdx) + } + prevEntry := entries[entryIdx-1] + if prevEntry[0] != typeSearchCheckpoint { + return fmt.Errorf("expected search checkpoint before canonical hash at entry %v but got %x", entryIdx, prevEntry) + } + return nil +} + +func invariantIncrementLogIdxIfNotImmediatelyAfterCanonicalHash(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entry[0] != typeInitiatingEvent { + return nil + } + if entryIdx == 0 { + return fmt.Errorf("found initiating event at index %v before any search checkpoint", entryIdx) + } + blockDiff := entry[1] + flags := entry[2] + incrementsLogIdx := flags&eventFlagIncrementLogIdx != 0 + prevEntry := entries[entryIdx-1] + prevEntryIsCanonicalHash := prevEntry[0] == typeCanonicalHash + if incrementsLogIdx && prevEntryIsCanonicalHash { + return fmt.Errorf("initiating event at index %v increments logIdx despite being immediately after canonical hash (prev entry %x)", entryIdx, prevEntry) + } + if incrementsLogIdx && blockDiff > 0 { + return fmt.Errorf("initiating event at index %v increments logIdx despite starting a new block", entryIdx) + } + if !incrementsLogIdx && !prevEntryIsCanonicalHash && blockDiff == 0 { + return fmt.Errorf("initiating event at index %v does not increment logIdx when block unchanged and not after canonical hash (prev entry %x)", entryIdx, prevEntry) + } + return nil +} + +func invariantExecLinkAfterInitEventWithFlagSet(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entry[0] != typeInitiatingEvent { + return nil + } + hasExecMessage := entry[2]&eventFlagHasExecutingMessage != 0 + if !hasExecMessage { + return nil + } + linkIdx := entryIdx + 1 + if linkIdx%searchCheckpointFrequency == 0 { + linkIdx += 2 // Skip over the search checkpoint and canonical hash events + } + if len(entries) <= linkIdx { + return fmt.Errorf("expected executing link after initiating event with exec msg flag set at entry %v but there were no more events", entryIdx) + } + if entries[linkIdx][0] != typeExecutingLink { + return fmt.Errorf("expected executing link at idx %v after initiating event with exec msg flag set at entry %v but got type %v", linkIdx, entryIdx, entries[linkIdx][0]) + } + return nil +} + +func invariantExecLinkOnlyAfterInitiatingEventWithFlagSet(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entry[0] != typeExecutingLink { + return nil + } + if entryIdx == 0 { + return errors.New("found executing link as first entry") + } + initIdx := entryIdx - 1 + if initIdx%searchCheckpointFrequency == 1 { + initIdx -= 2 // Skip the canonical hash and search checkpoint entries + } + if initIdx < 0 { + return fmt.Errorf("found executing link without a preceding initiating event at entry %v", entryIdx) + } + initEntry := entries[initIdx] + if initEntry[0] != typeInitiatingEvent { + return fmt.Errorf("expected initiating event at entry %v prior to executing link at %v but got %x", initIdx, entryIdx, initEntry[0]) + } + flags := initEntry[2] + if flags&eventFlagHasExecutingMessage == 0 { + return fmt.Errorf("initiating event at %v prior to executing link at %v does not have flag set to indicate needing a executing event: %x", initIdx, entryIdx, initEntry) + } + return nil +} + +func invariantExecCheckAfterExecLink(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entry[0] != typeExecutingLink { + return nil + } + checkIdx := entryIdx + 1 + if checkIdx%searchCheckpointFrequency == 0 { + checkIdx += 2 // Skip the search checkpoint and canonical hash entries + } + if checkIdx >= len(entries) { + return fmt.Errorf("expected executing link at %v to be followed by executing check at %v but ran out of entries", entryIdx, checkIdx) + } + checkEntry := entries[checkIdx] + if checkEntry[0] != typeExecutingCheck { + return fmt.Errorf("expected executing link at %v to be followed by executing check at %v but got type %v", entryIdx, checkIdx, checkEntry[0]) + } + return nil +} + +func invariantExecCheckOnlyAfterExecLink(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entry[0] != typeExecutingCheck { + return nil + } + if entryIdx == 0 { + return errors.New("found executing check as first entry") + } + linkIdx := entryIdx - 1 + if linkIdx%searchCheckpointFrequency == 1 { + linkIdx -= 2 // Skip the canonical hash and search checkpoint entries + } + if linkIdx < 0 { + return fmt.Errorf("found executing link without a preceding initiating event at entry %v", entryIdx) + } + linkEntry := entries[linkIdx] + if linkEntry[0] != typeExecutingLink { + return fmt.Errorf("expected executing link at entry %v prior to executing check at %v but got %x", linkIdx, entryIdx, linkEntry[0]) + } + return nil +} + +// invariantValidLastEntry checks that the last entry is either a executing check or initiating event with no exec message +func invariantValidLastEntry(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { + if entryIdx+1 < len(entries) { + return nil + } + if entry[0] == typeExecutingCheck { + return nil + } + if entry[0] != typeInitiatingEvent { + return fmt.Errorf("invalid final event type: %v", entry[0]) + } + evt, err := newInitiatingEventFromEntry(entry) + if err != nil { + return fmt.Errorf("final event was invalid: %w", err) + } + if evt.hasExecMsg { + return errors.New("ends with init event that should have exec msg but no exec msg follows") + } + return nil +}
diff --git OP/op-supervisor/supervisor/backend/db/db_test.go CELO/op-supervisor/supervisor/backend/db/db_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f07a5f4b9c8b8c34f8401a58e2d277619fb565e7 --- /dev/null +++ CELO/op-supervisor/supervisor/backend/db/db_test.go @@ -0,0 +1,923 @@ +package db + +import ( + "bytes" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func createTruncatedHash(i int) TruncatedHash { + return TruncateHash(createHash(i)) +} + +func createHash(i int) common.Hash { + data := bytes.Repeat([]byte{byte(i)}, common.HashLength) + return common.BytesToHash(data) +} + +func TestErrorOpeningDatabase(t *testing.T) { + dir := t.TempDir() + _, err := NewFromFile(testlog.Logger(t, log.LvlInfo), &stubMetrics{}, filepath.Join(dir, "missing-dir", "file.db")) + require.ErrorIs(t, err, os.ErrNotExist) +} + +func runDBTest(t *testing.T, setup func(t *testing.T, db *DB, m *stubMetrics), assert func(t *testing.T, db *DB, m *stubMetrics)) { + createDb := func(t *testing.T, dir string) (*DB, *stubMetrics, string) { + logger := testlog.Logger(t, log.LvlInfo) + path := filepath.Join(dir, "test.db") + m := &stubMetrics{} + db, err := NewFromFile(logger, m, path) + require.NoError(t, err, "Failed to create database") + t.Cleanup(func() { + err := db.Close() + if err != nil { + require.ErrorIs(t, err, fs.ErrClosed) + } + }) + return db, m, path + } + + t.Run("New", func(t *testing.T) { + db, m, _ := createDb(t, t.TempDir()) + setup(t, db, m) + assert(t, db, m) + }) + + t.Run("Existing", func(t *testing.T) { + dir := t.TempDir() + db, m, path := createDb(t, dir) + setup(t, db, m) + // Close and recreate the database + require.NoError(t, db.Close()) + checkDBInvariants(t, path, m) + + db2, m, path := createDb(t, dir) + assert(t, db2, m) + checkDBInvariants(t, path, m) + }) +} + +func TestEmptyDbDoesNotFindEntry(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) {}, + func(t *testing.T, db *DB, m *stubMetrics) { + requireNotContains(t, db, 0, 0, createHash(1)) + requireNotContains(t, db, 0, 0, common.Hash{}) + }) +} + +func TestAddLog(t *testing.T) { + t.Run("BlockZero", func(t *testing.T) { + // There are no logs in the genesis block so recording an entry for block 0 should be rejected. + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) {}, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 0}, 5000, 0, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("FirstEntry", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 15, 0, createHash(1)) + }) + }) + + t.Run("MultipleEntriesFromSameBlock", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil) + require.NoError(t, err) + err = db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 1, nil) + require.NoError(t, err) + err = db.AddLog(createTruncatedHash(3), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 2, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + require.EqualValues(t, 5, m.entryCount, "should not output new searchCheckpoint for every log") + requireContains(t, db, 15, 0, createHash(1)) + requireContains(t, db, 15, 1, createHash(2)) + requireContains(t, db, 15, 2, createHash(3)) + }) + }) + + t.Run("MultipleEntriesFromMultipleBlocks", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil) + require.NoError(t, err) + err = db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 1, nil) + require.NoError(t, err) + err = db.AddLog(createTruncatedHash(3), eth.BlockID{Hash: createHash(16), Number: 16}, 5002, 0, nil) + require.NoError(t, err) + err = db.AddLog(createTruncatedHash(4), eth.BlockID{Hash: createHash(16), Number: 16}, 5002, 1, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + require.EqualValues(t, 6, m.entryCount, "should not output new searchCheckpoint for every block") + requireContains(t, db, 15, 0, createHash(1)) + requireContains(t, db, 15, 1, createHash(2)) + requireContains(t, db, 16, 0, createHash(3)) + requireContains(t, db, 16, 1, createHash(4)) + }) + }) + + t.Run("ErrorWhenBeforeCurrentBlock", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(14), Number: 14}, 4998, 0, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("ErrorWhenBeforeCurrentBlockButAfterLastCheckpoint", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(13), Number: 13}, 5000, 0, nil) + require.NoError(t, err) + err = db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(14), Number: 14}, 4998, 0, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("ErrorWhenBeforeCurrentLogEvent", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 1, nil)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(14), Number: 15}, 4998, 0, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("ErrorWhenBeforeCurrentLogEventButAfterLastCheckpoint", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil) + require.NoError(t, err) + err = db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 1, nil) + require.NoError(t, err) + err = db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 2, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(14), Number: 15}, 4998, 1, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("ErrorWhenAtCurrentLogEvent", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 1, nil)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 4998, 1, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("ErrorWhenAtCurrentLogEventButAfterLastCheckpoint", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 2, nil)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(14), Number: 15}, 4998, 2, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("ErrorWhenSkippingLogEvent", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 4998, 2, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("ErrorWhenFirstLogIsNotLogIdxZero", func(t *testing.T) { + runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {}, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 4998, 5, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("ErrorWhenFirstLogOfNewBlockIsNotLogIdxZero", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(14), Number: 14}, 4996, 0, nil)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 4998, 1, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder) + }) + }) + + t.Run("MultipleSearchCheckpoints", func(t *testing.T) { + block1 := eth.BlockID{Hash: createHash(11), Number: 11} + block2 := eth.BlockID{Hash: createHash(12), Number: 12} + block3 := eth.BlockID{Hash: createHash(15), Number: 15} + block4 := eth.BlockID{Hash: createHash(16), Number: 16} + // First checkpoint is at entry idx 0 + // Block 1 logs don't reach the second checkpoint + block1LogCount := searchCheckpointFrequency - 10 + // Block 2 logs extend to just after the third checkpoint + block2LogCount := searchCheckpointFrequency + 20 + // Block 3 logs extend to immediately before the fourth checkpoint + block3LogCount := searchCheckpointFrequency - 16 + block4LogCount := 2 + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + for i := 0; i < block1LogCount; i++ { + err := db.AddLog(createTruncatedHash(i), block1, 3000, uint32(i), nil) + require.NoErrorf(t, err, "failed to add log %v of block 1", i) + } + for i := 0; i < block2LogCount; i++ { + err := db.AddLog(createTruncatedHash(i), block2, 3002, uint32(i), nil) + require.NoErrorf(t, err, "failed to add log %v of block 2", i) + } + for i := 0; i < block3LogCount; i++ { + err := db.AddLog(createTruncatedHash(i), block3, 3004, uint32(i), nil) + require.NoErrorf(t, err, "failed to add log %v of block 3", i) + } + // Verify that we're right before the fourth checkpoint will be written. + // entryCount is the number of entries, so given 0 based indexing is the index of the next entry + // the first checkpoint is at entry 0, the second at entry searchCheckpointFrequency etc + // so the fourth is at entry 3*searchCheckpointFrequency + require.EqualValues(t, 3*searchCheckpointFrequency, m.entryCount) + for i := 0; i < block4LogCount; i++ { + err := db.AddLog(createTruncatedHash(i), block4, 3006, uint32(i), nil) + require.NoErrorf(t, err, "failed to add log %v of block 4", i) + } + }, + func(t *testing.T, db *DB, m *stubMetrics) { + // Check that we wrote additional search checkpoints + expectedCheckpointCount := 4 + expectedEntryCount := block1LogCount + block2LogCount + block3LogCount + block4LogCount + (2 * expectedCheckpointCount) + require.EqualValues(t, expectedEntryCount, m.entryCount) + // Check we can find all the logs. + for i := 0; i < block1LogCount; i++ { + requireContains(t, db, block1.Number, uint32(i), createHash(i)) + } + // Block 2 logs extend to just after the third checkpoint + for i := 0; i < block2LogCount; i++ { + requireContains(t, db, block2.Number, uint32(i), createHash(i)) + } + // Block 3 logs extend to immediately before the fourth checkpoint + for i := 0; i < block3LogCount; i++ { + requireContains(t, db, block3.Number, uint32(i), createHash(i)) + } + // Block 4 logs start immediately after the fourth checkpoint + for i := 0; i < block4LogCount; i++ { + requireContains(t, db, block4.Number, uint32(i), createHash(i)) + } + }) + }) +} + +func TestAddDependentLog(t *testing.T) { + execMsg := ExecutingMessage{ + Chain: 3, + BlockNum: 42894, + LogIdx: 42, + Timestamp: 8742482, + Hash: TruncateHash(createHash(8844)), + } + t.Run("FirstEntry", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, &execMsg) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 15, 0, createHash(1), execMsg) + }) + }) + + t.Run("CheckpointBetweenInitEventAndExecLink", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + for i := uint32(0); m.entryCount < searchCheckpointFrequency-1; i++ { + require.NoError(t, db.AddLog(createTruncatedHash(9), eth.BlockID{Hash: createHash(9), Number: 1}, 500, i, nil)) + } + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, &execMsg) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 15, 0, createHash(1), execMsg) + }) + }) + + t.Run("CheckpointBetweenInitEventAndExecLinkNotIncrementingBlock", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + + for i := uint32(0); m.entryCount < searchCheckpointFrequency-1; i++ { + require.NoError(t, db.AddLog(createTruncatedHash(9), eth.BlockID{Hash: createHash(9), Number: 1}, 500, i, nil)) + } + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 1}, 5000, 253, &execMsg) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 1, 253, createHash(1), execMsg) + }) + }) + + t.Run("CheckpointBetweenExecLinkAndExecCheck", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + for i := uint32(0); m.entryCount < searchCheckpointFrequency-2; i++ { + require.NoError(t, db.AddLog(createTruncatedHash(9), eth.BlockID{Hash: createHash(9), Number: 1}, 500, i, nil)) + } + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 15}, 5000, 0, &execMsg) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 15, 0, createHash(1), execMsg) + }) + }) + + t.Run("CheckpointBetweenExecLinkAndExecCheckNotIncrementingBlock", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + for i := uint32(0); m.entryCount < searchCheckpointFrequency-2; i++ { + require.NoError(t, db.AddLog(createTruncatedHash(9), eth.BlockID{Hash: createHash(9), Number: 1}, 500, i, nil)) + } + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(15), Number: 1}, 5000, 252, &execMsg) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 1, 252, createHash(1), execMsg) + }) + }) +} + +func TestContains(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(3), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 2, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(52), Number: 52}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(3), eth.BlockID{Hash: createHash(52), Number: 52}, 500, 1, nil)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + // Should find added logs + requireContains(t, db, 50, 0, createHash(1)) + requireContains(t, db, 50, 1, createHash(3)) + requireContains(t, db, 50, 2, createHash(2)) + requireContains(t, db, 52, 0, createHash(1)) + requireContains(t, db, 52, 1, createHash(3)) + + // Should not find log when block number too low + requireNotContains(t, db, 49, 0, createHash(1)) + + // Should not find log when block number too high + requireNotContains(t, db, 51, 0, createHash(1)) + + // Should not find log when requested log after end of database + requireNotContains(t, db, 52, 2, createHash(3)) + requireNotContains(t, db, 53, 0, createHash(3)) + + // Should not find log when log index too high + requireNotContains(t, db, 50, 3, createHash(2)) + + // Should not find log when hash doesn't match log at block number and index + requireWrongHash(t, db, 50, 0, createHash(5), ExecutingMessage{}) + }) +} + +func TestExecutes(t *testing.T) { + execMsg1 := ExecutingMessage{ + Chain: 33, + BlockNum: 22, + LogIdx: 99, + Timestamp: 948294, + Hash: createTruncatedHash(332299), + } + execMsg2 := ExecutingMessage{ + Chain: 44, + BlockNum: 55, + LogIdx: 66, + Timestamp: 77777, + Hash: createTruncatedHash(445566), + } + execMsg3 := ExecutingMessage{ + Chain: 77, + BlockNum: 88, + LogIdx: 89, + Timestamp: 6578567, + Hash: createTruncatedHash(778889), + } + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(3), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 1, &execMsg1)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 2, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(52), Number: 52}, 500, 0, &execMsg2)) + require.NoError(t, db.AddLog(createTruncatedHash(3), eth.BlockID{Hash: createHash(52), Number: 52}, 500, 1, &execMsg3)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + // Should find added logs + requireExecutingMessage(t, db, 50, 0, ExecutingMessage{}) + requireExecutingMessage(t, db, 50, 1, execMsg1) + requireExecutingMessage(t, db, 50, 2, ExecutingMessage{}) + requireExecutingMessage(t, db, 52, 0, execMsg2) + requireExecutingMessage(t, db, 52, 1, execMsg3) + + // Should not find log when block number too low + requireNotContains(t, db, 49, 0, createHash(1)) + + // Should not find log when block number too high + requireNotContains(t, db, 51, 0, createHash(1)) + + // Should not find log when requested log after end of database + requireNotContains(t, db, 52, 2, createHash(3)) + requireNotContains(t, db, 53, 0, createHash(3)) + + // Should not find log when log index too high + requireNotContains(t, db, 50, 3, createHash(2)) + }) +} + +func TestGetBlockInfo(t *testing.T) { + t.Run("ReturnsEOFWhenEmpty", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) {}, + func(t *testing.T, db *DB, m *stubMetrics) { + _, _, err := db.ClosestBlockInfo(10) + require.ErrorIs(t, err, io.EOF) + }) + }) + + t.Run("ReturnsEOFWhenRequestedBlockBeforeFirstSearchCheckpoint", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(11), Number: 11}, 500, 0, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + _, _, err := db.ClosestBlockInfo(10) + require.ErrorIs(t, err, io.EOF) + }) + }) + + t.Run("ReturnFirstBlockInfo", func(t *testing.T) { + block := eth.BlockID{Hash: createHash(11), Number: 11} + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(1), block, 500, 0, nil) + require.NoError(t, err) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireClosestBlockInfo(t, db, 11, block.Number, block.Hash) + requireClosestBlockInfo(t, db, 12, block.Number, block.Hash) + requireClosestBlockInfo(t, db, 200, block.Number, block.Hash) + }) + }) + + t.Run("ReturnClosestCheckpointBlockInfo", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + for i := 1; i < searchCheckpointFrequency+3; i++ { + block := eth.BlockID{Hash: createHash(i), Number: uint64(i)} + err := db.AddLog(createTruncatedHash(i), block, uint64(i)*2, 0, nil) + require.NoError(t, err) + } + }, + func(t *testing.T, db *DB, m *stubMetrics) { + // Expect block from the first checkpoint + requireClosestBlockInfo(t, db, 1, 1, createHash(1)) + requireClosestBlockInfo(t, db, 10, 1, createHash(1)) + requireClosestBlockInfo(t, db, searchCheckpointFrequency-3, 1, createHash(1)) + + // Expect block from the second checkpoint + // 2 entries used for initial checkpoint but we start at block 1 + secondCheckpointBlockNum := searchCheckpointFrequency - 1 + requireClosestBlockInfo(t, db, uint64(secondCheckpointBlockNum), uint64(secondCheckpointBlockNum), createHash(secondCheckpointBlockNum)) + requireClosestBlockInfo(t, db, uint64(secondCheckpointBlockNum)+1, uint64(secondCheckpointBlockNum), createHash(secondCheckpointBlockNum)) + requireClosestBlockInfo(t, db, uint64(secondCheckpointBlockNum)+2, uint64(secondCheckpointBlockNum), createHash(secondCheckpointBlockNum)) + }) + }) +} + +func requireClosestBlockInfo(t *testing.T, db *DB, searchFor uint64, expectedBlockNum uint64, expectedHash common.Hash) { + blockNum, hash, err := db.ClosestBlockInfo(searchFor) + require.NoError(t, err) + require.Equal(t, expectedBlockNum, blockNum) + require.Equal(t, TruncateHash(expectedHash), hash) +} + +func requireContains(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash, execMsg ...ExecutingMessage) { + require.LessOrEqual(t, len(execMsg), 1, "cannot have multiple executing messages for a single log") + m, ok := db.m.(*stubMetrics) + require.True(t, ok, "Did not get the expected metrics type") + result, err := db.Contains(blockNum, logIdx, TruncateHash(logHash)) + require.NoErrorf(t, err, "Error searching for log %v in block %v", logIdx, blockNum) + require.Truef(t, result, "Did not find log %v in block %v with hash %v", logIdx, blockNum, logHash) + require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency), "Should not need to read more than between two checkpoints") + require.NotZero(t, m.entriesReadForSearch, "Must read at least some entries to find the log") + + var expectedExecMsg ExecutingMessage + if len(execMsg) == 1 { + expectedExecMsg = execMsg[0] + } + requireExecutingMessage(t, db, blockNum, logIdx, expectedExecMsg) +} + +func requireNotContains(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash) { + m, ok := db.m.(*stubMetrics) + require.True(t, ok, "Did not get the expected metrics type") + result, err := db.Contains(blockNum, logIdx, TruncateHash(logHash)) + require.NoErrorf(t, err, "Error searching for log %v in block %v", logIdx, blockNum) + require.Falsef(t, result, "Found unexpected log %v in block %v with hash %v", logIdx, blockNum, logHash) + require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency), "Should not need to read more than between two checkpoints") + + _, err = db.Executes(blockNum, logIdx) + require.ErrorIs(t, err, ErrNotFound, "Found unexpected log when getting executing message") + require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency), "Should not need to read more than between two checkpoints") +} + +func requireExecutingMessage(t *testing.T, db *DB, blockNum uint64, logIdx uint32, execMsg ExecutingMessage) { + m, ok := db.m.(*stubMetrics) + require.True(t, ok, "Did not get the expected metrics type") + actualExecMsg, err := db.Executes(blockNum, logIdx) + require.NoError(t, err, "Error when searching for executing message") + require.Equal(t, execMsg, actualExecMsg, "Should return matching executing message") + require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency), "Should not need to read more than between two checkpoints") + require.NotZero(t, m.entriesReadForSearch, "Must read at least some entries to find the log") +} + +func requireWrongHash(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash, execMsg ExecutingMessage) { + m, ok := db.m.(*stubMetrics) + require.True(t, ok, "Did not get the expected metrics type") + result, err := db.Contains(blockNum, logIdx, TruncateHash(logHash)) + require.NoErrorf(t, err, "Error searching for log %v in block %v", logIdx, blockNum) + require.Falsef(t, result, "Found unexpected log %v in block %v with hash %v", logIdx, blockNum, logHash) + + _, err = db.Executes(blockNum, logIdx) + require.NoError(t, err, "Error when searching for executing message") + require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency), "Should not need to read more than between two checkpoints") +} + +func TestRecoverOnCreate(t *testing.T) { + createDb := func(t *testing.T, store *stubEntryStore) (*DB, *stubMetrics, error) { + logger := testlog.Logger(t, log.LvlInfo) + m := &stubMetrics{} + db, err := NewFromEntryStore(logger, m, store) + return db, m, err + } + + validInitEvent, err := newInitiatingEvent(logContext{blockNum: 1, logIdx: 0}, 1, 0, createTruncatedHash(1), false) + require.NoError(t, err) + validEventSequence := []entrydb.Entry{ + newSearchCheckpoint(1, 0, 100).encode(), + newCanonicalHash(createTruncatedHash(344)).encode(), + validInitEvent.encode(), + } + var emptyEventSequence []entrydb.Entry + + for _, prefixEvents := range [][]entrydb.Entry{emptyEventSequence, validEventSequence} { + prefixEvents := prefixEvents + storeWithEvents := func(evts ...entrydb.Entry) *stubEntryStore { + store := &stubEntryStore{} + store.entries = append(store.entries, prefixEvents...) + store.entries = append(store.entries, evts...) + return store + } + t.Run(fmt.Sprintf("PrefixEvents-%v", len(prefixEvents)), func(t *testing.T) { + t.Run("NoTruncateWhenLastEntryIsLogWithNoExecMessage", func(t *testing.T) { + initEvent, err := newInitiatingEvent(logContext{blockNum: 3, logIdx: 0}, 3, 0, createTruncatedHash(1), false) + require.NoError(t, err) + store := storeWithEvents( + newSearchCheckpoint(3, 0, 100).encode(), + newCanonicalHash(createTruncatedHash(344)).encode(), + initEvent.encode(), + ) + db, m, err := createDb(t, store) + require.NoError(t, err) + require.EqualValues(t, len(prefixEvents)+3, m.entryCount) + requireContains(t, db, 3, 0, createHash(1)) + }) + + t.Run("NoTruncateWhenLastEntryIsExecutingCheck", func(t *testing.T) { + initEvent, err := newInitiatingEvent(logContext{blockNum: 3, logIdx: 0}, 3, 0, createTruncatedHash(1), true) + execMsg := ExecutingMessage{ + Chain: 4, + BlockNum: 10, + LogIdx: 4, + Timestamp: 1288, + Hash: createTruncatedHash(4), + } + require.NoError(t, err) + linkEvt, err := newExecutingLink(execMsg) + require.NoError(t, err) + store := storeWithEvents( + newSearchCheckpoint(3, 0, 100).encode(), + newCanonicalHash(createTruncatedHash(344)).encode(), + initEvent.encode(), + linkEvt.encode(), + newExecutingCheck(execMsg.Hash).encode(), + ) + db, m, err := createDb(t, store) + require.NoError(t, err) + require.EqualValues(t, len(prefixEvents)+5, m.entryCount) + requireContains(t, db, 3, 0, createHash(1), execMsg) + }) + + t.Run("TruncateWhenLastEntrySearchCheckpoint", func(t *testing.T) { + store := storeWithEvents(newSearchCheckpoint(3, 0, 100).encode()) + _, m, err := createDb(t, store) + require.NoError(t, err) + require.EqualValues(t, len(prefixEvents), m.entryCount) + }) + + t.Run("TruncateWhenLastEntryCanonicalHash", func(t *testing.T) { + store := storeWithEvents( + newSearchCheckpoint(3, 0, 100).encode(), + newCanonicalHash(createTruncatedHash(344)).encode(), + ) + _, m, err := createDb(t, store) + require.NoError(t, err) + require.EqualValues(t, len(prefixEvents), m.entryCount) + }) + + t.Run("TruncateWhenLastEntryInitEventWithExecMsg", func(t *testing.T) { + initEvent, err := newInitiatingEvent(logContext{blockNum: 3, logIdx: 0}, 3, 0, createTruncatedHash(1), true) + require.NoError(t, err) + store := storeWithEvents( + newSearchCheckpoint(3, 0, 100).encode(), + newCanonicalHash(createTruncatedHash(344)).encode(), + initEvent.encode(), + ) + _, m, err := createDb(t, store) + require.NoError(t, err) + require.EqualValues(t, len(prefixEvents), m.entryCount) + }) + + t.Run("TruncateWhenLastEntryInitEventWithExecLink", func(t *testing.T) { + initEvent, err := newInitiatingEvent(logContext{blockNum: 3, logIdx: 0}, 3, 0, createTruncatedHash(1), true) + require.NoError(t, err) + execMsg := ExecutingMessage{ + Chain: 4, + BlockNum: 10, + LogIdx: 4, + Timestamp: 1288, + Hash: createTruncatedHash(4), + } + require.NoError(t, err) + linkEvt, err := newExecutingLink(execMsg) + require.NoError(t, err) + store := storeWithEvents( + newSearchCheckpoint(3, 0, 100).encode(), + newCanonicalHash(createTruncatedHash(344)).encode(), + initEvent.encode(), + linkEvt.encode(), + ) + _, m, err := createDb(t, store) + require.NoError(t, err) + require.EqualValues(t, len(prefixEvents), m.entryCount) + }) + }) + } +} + +func TestRewind(t *testing.T) { + t.Run("WhenEmpty", func(t *testing.T) { + runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {}, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.Rewind(100)) + require.NoError(t, db.Rewind(0)) + }) + }) + + t.Run("AfterLastBlock", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(3), eth.BlockID{Hash: createHash(51), Number: 51}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(4), eth.BlockID{Hash: createHash(74), Number: 74}, 700, 0, nil)) + require.NoError(t, db.Rewind(75)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 50, 0, createHash(1)) + requireContains(t, db, 50, 1, createHash(2)) + requireContains(t, db, 51, 0, createHash(3)) + requireContains(t, db, 74, 0, createHash(4)) + }) + }) + + t.Run("BeforeFirstBlock", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 1, nil)) + require.NoError(t, db.Rewind(25)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireNotContains(t, db, 50, 0, createHash(1)) + requireNotContains(t, db, 50, 0, createHash(1)) + require.Zero(t, m.entryCount) + }) + }) + + t.Run("AtFirstBlock", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(51), Number: 51}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(51), Number: 51}, 502, 1, nil)) + require.NoError(t, db.Rewind(50)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 50, 0, createHash(1)) + requireContains(t, db, 50, 1, createHash(2)) + requireNotContains(t, db, 51, 0, createHash(1)) + requireNotContains(t, db, 51, 1, createHash(2)) + }) + }) + + t.Run("AtSecondCheckpoint", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + for i := uint32(0); m.entryCount < searchCheckpointFrequency; i++ { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(50), Number: 50}, 500, i, nil)) + } + require.EqualValues(t, searchCheckpointFrequency, m.entryCount) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(51), Number: 51}, 502, 0, nil)) + require.EqualValues(t, searchCheckpointFrequency+3, m.entryCount, "Should have inserted new checkpoint and extra log") + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(51), Number: 51}, 502, 1, nil)) + require.NoError(t, db.Rewind(50)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + require.EqualValues(t, searchCheckpointFrequency, m.entryCount, "Should have deleted second checkpoint") + requireContains(t, db, 50, 0, createHash(1)) + requireContains(t, db, 50, 1, createHash(1)) + requireNotContains(t, db, 51, 0, createHash(1)) + requireNotContains(t, db, 51, 1, createHash(2)) + }) + }) + + t.Run("BetweenLogEntries", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 1, nil)) + require.NoError(t, db.Rewind(55)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 50, 0, createHash(1)) + requireContains(t, db, 50, 1, createHash(2)) + requireNotContains(t, db, 60, 0, createHash(1)) + requireNotContains(t, db, 60, 1, createHash(2)) + }) + }) + + t.Run("AtExistingLogEntry", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(59), Number: 59}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(59), Number: 59}, 500, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(61), Number: 61}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(61), Number: 61}, 502, 1, nil)) + require.NoError(t, db.Rewind(60)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 59, 0, createHash(1)) + requireContains(t, db, 59, 1, createHash(2)) + requireContains(t, db, 60, 0, createHash(1)) + requireContains(t, db, 60, 1, createHash(2)) + requireNotContains(t, db, 61, 0, createHash(1)) + requireNotContains(t, db, 61, 1, createHash(2)) + }) + }) + + t.Run("AtLastEntry", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(50), Number: 50}, 500, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(70), Number: 70}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(70), Number: 70}, 502, 1, nil)) + require.NoError(t, db.Rewind(70)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + requireContains(t, db, 50, 0, createHash(1)) + requireContains(t, db, 50, 1, createHash(2)) + requireContains(t, db, 60, 0, createHash(1)) + requireContains(t, db, 60, 1, createHash(2)) + requireContains(t, db, 70, 0, createHash(1)) + requireContains(t, db, 70, 1, createHash(2)) + }) + }) + + t.Run("ReaddDeletedBlocks", func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(59), Number: 59}, 500, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(59), Number: 59}, 500, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 1, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(61), Number: 61}, 502, 0, nil)) + require.NoError(t, db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(61), Number: 61}, 502, 1, nil)) + require.NoError(t, db.Rewind(60)) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + err := db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(59), Number: 59}, 500, 1, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder, "Cannot add block before rewound head") + err = db.AddLog(createTruncatedHash(2), eth.BlockID{Hash: createHash(60), Number: 60}, 502, 1, nil) + require.ErrorIs(t, err, ErrLogOutOfOrder, "Cannot add block that was rewound to") + err = db.AddLog(createTruncatedHash(1), eth.BlockID{Hash: createHash(60), Number: 61}, 502, 0, nil) + require.NoError(t, err, "Can re-add deleted block") + }) + }) +} + +type stubMetrics struct { + entryCount int64 + entriesReadForSearch int64 +} + +func (s *stubMetrics) RecordEntryCount(count int64) { + s.entryCount = count +} + +func (s *stubMetrics) RecordSearchEntriesRead(count int64) { + s.entriesReadForSearch = count +} + +var _ Metrics = (*stubMetrics)(nil) + +type stubEntryStore struct { + entries []entrydb.Entry +} + +func (s *stubEntryStore) Size() int64 { + return int64(len(s.entries)) +} + +func (s *stubEntryStore) Read(idx int64) (entrydb.Entry, error) { + if idx < int64(len(s.entries)) { + return s.entries[idx], nil + } + return entrydb.Entry{}, io.EOF +} + +func (s *stubEntryStore) Append(entries ...entrydb.Entry) error { + s.entries = append(s.entries, entries...) + return nil +} + +func (s *stubEntryStore) Truncate(idx int64) error { + s.entries = s.entries[:min(s.Size()-1, idx+1)] + return nil +} + +func (s *stubEntryStore) Close() error { + return nil +} + +var _ EntryStore = (*stubEntryStore)(nil)
diff --git OP/op-supervisor/supervisor/backend/db/entries.go CELO/op-supervisor/supervisor/backend/db/entries.go new file mode 100644 index 0000000000000000000000000000000000000000..2bb3d3a1e843b3ae96d2387b1a4e72b45ad56eb6 --- /dev/null +++ CELO/op-supervisor/supervisor/backend/db/entries.go @@ -0,0 +1,250 @@ +package db + +import ( + "encoding/binary" + "fmt" + "math" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" +) + +type searchCheckpoint struct { + blockNum uint64 + logIdx uint32 + timestamp uint64 +} + +func newSearchCheckpoint(blockNum uint64, logIdx uint32, timestamp uint64) searchCheckpoint { + return searchCheckpoint{ + blockNum: blockNum, + logIdx: logIdx, + timestamp: timestamp, + } +} + +func newSearchCheckpointFromEntry(data entrydb.Entry) (searchCheckpoint, error) { + if data[0] != typeSearchCheckpoint { + return searchCheckpoint{}, fmt.Errorf("%w: attempting to decode search checkpoint but was type %v", ErrDataCorruption, data[0]) + } + return searchCheckpoint{ + blockNum: binary.LittleEndian.Uint64(data[1:9]), + logIdx: binary.LittleEndian.Uint32(data[9:13]), + timestamp: binary.LittleEndian.Uint64(data[13:21]), + }, nil +} + +// encode creates a search checkpoint entry +// type 0: "search checkpoint" <type><uint64 block number: 8 bytes><uint32 event index offset: 4 bytes><uint64 timestamp: 8 bytes> = 20 bytes +func (s searchCheckpoint) encode() entrydb.Entry { + var data entrydb.Entry + data[0] = typeSearchCheckpoint + binary.LittleEndian.PutUint64(data[1:9], s.blockNum) + binary.LittleEndian.PutUint32(data[9:13], s.logIdx) + binary.LittleEndian.PutUint64(data[13:21], s.timestamp) + return data +} + +type canonicalHash struct { + hash TruncatedHash +} + +func newCanonicalHash(hash TruncatedHash) canonicalHash { + return canonicalHash{hash: hash} +} + +func newCanonicalHashFromEntry(data entrydb.Entry) (canonicalHash, error) { + if data[0] != typeCanonicalHash { + return canonicalHash{}, fmt.Errorf("%w: attempting to decode canonical hash but was type %v", ErrDataCorruption, data[0]) + } + var truncated TruncatedHash + copy(truncated[:], data[1:21]) + return newCanonicalHash(truncated), nil +} + +func (c canonicalHash) encode() entrydb.Entry { + var entry entrydb.Entry + entry[0] = typeCanonicalHash + copy(entry[1:21], c.hash[:]) + return entry +} + +type initiatingEvent struct { + blockDiff uint8 + incrementLogIdx bool + hasExecMsg bool + logHash TruncatedHash +} + +func newInitiatingEventFromEntry(data entrydb.Entry) (initiatingEvent, error) { + if data[0] != typeInitiatingEvent { + return initiatingEvent{}, fmt.Errorf("%w: attempting to decode initiating event but was type %v", ErrDataCorruption, data[0]) + } + blockNumDiff := data[1] + flags := data[2] + return initiatingEvent{ + blockDiff: blockNumDiff, + incrementLogIdx: flags&eventFlagIncrementLogIdx != 0, + hasExecMsg: flags&eventFlagHasExecutingMessage != 0, + logHash: TruncatedHash(data[3:23]), + }, nil +} + +func newInitiatingEvent(pre logContext, blockNum uint64, logIdx uint32, logHash TruncatedHash, hasExecMsg bool) (initiatingEvent, error) { + blockDiff := blockNum - pre.blockNum + if blockDiff > math.MaxUint8 { + // TODO(optimism#10857): Need to find a way to support this. + return initiatingEvent{}, fmt.Errorf("too many block skipped between %v and %v", pre.blockNum, blockNum) + } + + currLogIdx := pre.logIdx + if blockDiff > 0 { + currLogIdx = 0 + } + logDiff := logIdx - currLogIdx + if logDiff > 1 { + return initiatingEvent{}, fmt.Errorf("skipped logs between %v and %v", currLogIdx, logIdx) + } + + return initiatingEvent{ + blockDiff: uint8(blockDiff), + incrementLogIdx: logDiff > 0, + hasExecMsg: hasExecMsg, + logHash: logHash, + }, nil +} + +// encode creates an initiating event entry +// type 2: "initiating event" <type><blocknum diff: 1 byte><event flags: 1 byte><event-hash: 20 bytes> = 23 bytes +func (i initiatingEvent) encode() entrydb.Entry { + var data entrydb.Entry + data[0] = typeInitiatingEvent + data[1] = i.blockDiff + flags := byte(0) + if i.incrementLogIdx { + // Set flag to indicate log idx needs to be incremented (ie we're not directly after a checkpoint) + flags = flags | eventFlagIncrementLogIdx + } + if i.hasExecMsg { + flags = flags | eventFlagHasExecutingMessage + } + data[2] = flags + copy(data[3:23], i.logHash[:]) + return data +} + +func (i initiatingEvent) postContext(pre logContext) logContext { + post := logContext{ + blockNum: pre.blockNum + uint64(i.blockDiff), + logIdx: pre.logIdx, + } + if i.blockDiff > 0 { + post.logIdx = 0 + } + if i.incrementLogIdx { + post.logIdx++ + } + return post +} + +// preContext is the reverse of postContext and calculates the logContext required as input to get the specified post +// context after applying this init event. +func (i initiatingEvent) preContext(post logContext) logContext { + pre := post + pre.blockNum = post.blockNum - uint64(i.blockDiff) + if i.incrementLogIdx { + pre.logIdx-- + } + return pre +} + +type executingLink struct { + chain uint32 + blockNum uint64 + logIdx uint32 + timestamp uint64 +} + +func newExecutingLink(msg ExecutingMessage) (executingLink, error) { + if msg.LogIdx > 1<<24 { + return executingLink{}, fmt.Errorf("log idx is too large (%v)", msg.LogIdx) + } + return executingLink{ + chain: msg.Chain, + blockNum: msg.BlockNum, + logIdx: msg.LogIdx, + timestamp: msg.Timestamp, + }, nil +} + +func newExecutingLinkFromEntry(data entrydb.Entry) (executingLink, error) { + if data[0] != typeExecutingLink { + return executingLink{}, fmt.Errorf("%w: attempting to decode executing link but was type %v", ErrDataCorruption, data[0]) + } + timestamp := binary.LittleEndian.Uint64(data[16:24]) + return executingLink{ + chain: binary.LittleEndian.Uint32(data[1:5]), + blockNum: binary.LittleEndian.Uint64(data[5:13]), + logIdx: uint32(data[13]) | uint32(data[14])<<8 | uint32(data[15])<<16, + timestamp: timestamp, + }, nil +} + +// encode creates an executing link entry +// type 3: "executing link" <type><chain: 4 bytes><blocknum: 8 bytes><event index: 3 bytes><uint64 timestamp: 8 bytes> = 24 bytes +func (e executingLink) encode() entrydb.Entry { + var entry entrydb.Entry + entry[0] = typeExecutingLink + binary.LittleEndian.PutUint32(entry[1:5], e.chain) + binary.LittleEndian.PutUint64(entry[5:13], e.blockNum) + + entry[13] = byte(e.logIdx) + entry[14] = byte(e.logIdx >> 8) + entry[15] = byte(e.logIdx >> 16) + + binary.LittleEndian.PutUint64(entry[16:24], e.timestamp) + return entry +} + +type executingCheck struct { + hash TruncatedHash +} + +func newExecutingCheck(hash TruncatedHash) executingCheck { + return executingCheck{hash: hash} +} + +func newExecutingCheckFromEntry(entry entrydb.Entry) (executingCheck, error) { + if entry[0] != typeExecutingCheck { + return executingCheck{}, fmt.Errorf("%w: attempting to decode executing check but was type %v", ErrDataCorruption, entry[0]) + } + var hash TruncatedHash + copy(hash[:], entry[1:21]) + return newExecutingCheck(hash), nil +} + +// encode creates an executing check entry +// type 4: "executing check" <type><event-hash: 20 bytes> = 21 bytes +func (e executingCheck) encode() entrydb.Entry { + var entry entrydb.Entry + entry[0] = typeExecutingCheck + copy(entry[1:21], e.hash[:]) + return entry +} + +func newExecutingMessageFromEntries(linkEntry entrydb.Entry, checkEntry entrydb.Entry) (ExecutingMessage, error) { + link, err := newExecutingLinkFromEntry(linkEntry) + if err != nil { + return ExecutingMessage{}, fmt.Errorf("invalid executing link: %w", err) + } + check, err := newExecutingCheckFromEntry(checkEntry) + if err != nil { + return ExecutingMessage{}, fmt.Errorf("invalid executing check: %w", err) + } + return ExecutingMessage{ + Chain: link.chain, + BlockNum: link.blockNum, + LogIdx: link.logIdx, + Timestamp: link.timestamp, + Hash: check.hash, + }, nil +}
diff --git OP/op-supervisor/supervisor/backend/db/entrydb/entry_db.go CELO/op-supervisor/supervisor/backend/db/entrydb/entry_db.go new file mode 100644 index 0000000000000000000000000000000000000000..43d22e457e652978e5ab12d0fe47b1f6e6c104c2 --- /dev/null +++ CELO/op-supervisor/supervisor/backend/db/entrydb/entry_db.go @@ -0,0 +1,135 @@ +package entrydb + +import ( + "errors" + "fmt" + "io" + "os" + + "github.com/ethereum/go-ethereum/log" +) + +const ( + EntrySize = 24 +) + +type Entry [EntrySize]byte + +// dataAccess defines a minimal API required to manipulate the actual stored data. +// It is a subset of the os.File API but could (theoretically) be satisfied by an in-memory implementation for testing. +type dataAccess interface { + io.ReaderAt + io.Writer + io.Closer + Truncate(size int64) error +} + +type EntryDB struct { + data dataAccess + size int64 + + cleanupFailedWrite bool +} + +// NewEntryDB creates an EntryDB. A new file will be created if the specified path does not exist, +// but parent directories will not be created. +// If the file exists it will be used as the existing data. +// Returns ErrRecoveryRequired if the existing file is not a valid entry db. A EntryDB is still returned but all +// operations will return ErrRecoveryRequired until the Recover method is called. +func NewEntryDB(logger log.Logger, path string) (*EntryDB, error) { + logger.Info("Opening entry database", "path", path) + file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to open database at %v: %w", path, err) + } + info, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("failed to stat database at %v: %w", path, err) + } + size := info.Size() / EntrySize + db := &EntryDB{ + data: file, + size: size, + } + if size*EntrySize != info.Size() { + logger.Warn("File size (%v) is nut a multiple of entry size %v. Truncating to last complete entry", size, EntrySize) + if err := db.recover(); err != nil { + return nil, fmt.Errorf("failed to recover database at %v: %w", path, err) + } + } + return db, nil +} + +func (e *EntryDB) Size() int64 { + return e.size +} + +// Read an entry from the database by index. Returns io.EOF iff idx is after the last entry. +func (e *EntryDB) Read(idx int64) (Entry, error) { + if idx >= e.size { + return Entry{}, io.EOF + } + var out Entry + read, err := e.data.ReadAt(out[:], idx*EntrySize) + // Ignore io.EOF if we read the entire last entry as ReadAt may return io.EOF or nil when it reads the last byte + if err != nil && !(errors.Is(err, io.EOF) && read == EntrySize) { + return Entry{}, fmt.Errorf("failed to read entry %v: %w", idx, err) + } + return out, nil +} + +// Append entries to the database. +// The entries are combined in memory and passed to a single Write invocation. +// If the write fails, it will attempt to truncate any partially written data. +// Subsequent writes to this instance will fail until partially written data is truncated. +func (e *EntryDB) Append(entries ...Entry) error { + if e.cleanupFailedWrite { + // Try to rollback partially written data from a previous Append + if truncateErr := e.Truncate(e.size - 1); truncateErr != nil { + return fmt.Errorf("failed to recover from previous write error: %w", truncateErr) + } + } + data := make([]byte, 0, len(entries)*EntrySize) + for _, entry := range entries { + data = append(data, entry[:]...) + } + if n, err := e.data.Write(data); err != nil { + if n == 0 { + // Didn't write any data, so no recovery required + return err + } + // Try to rollback the partially written data + if truncateErr := e.Truncate(e.size - 1); truncateErr != nil { + // Failed to rollback, set a flag to attempt the clean up on the next write + e.cleanupFailedWrite = true + return errors.Join(err, fmt.Errorf("failed to remove partially written data: %w", truncateErr)) + } + // Successfully rolled back the changes, still report the failed write + return err + } + e.size += int64(len(entries)) + return nil +} + +// Truncate the database so that the last retained entry is idx. Any entries after idx are deleted. +func (e *EntryDB) Truncate(idx int64) error { + if err := e.data.Truncate((idx + 1) * EntrySize); err != nil { + return fmt.Errorf("failed to truncate to entry %v: %w", idx, err) + } + // Update the lastEntryIdx cache + e.size = idx + 1 + e.cleanupFailedWrite = false + return nil +} + +// recover an invalid database by truncating back to the last complete event. +func (e *EntryDB) recover() error { + if err := e.data.Truncate((e.size) * EntrySize); err != nil { + return fmt.Errorf("failed to truncate trailing partial entries: %w", err) + } + return nil +} + +func (e *EntryDB) Close() error { + return e.data.Close() +}
diff --git OP/op-supervisor/supervisor/backend/db/entrydb/entry_db_test.go CELO/op-supervisor/supervisor/backend/db/entrydb/entry_db_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9f7dddccbb4304d7642e5567f13768d7bcda104c --- /dev/null +++ CELO/op-supervisor/supervisor/backend/db/entrydb/entry_db_test.go @@ -0,0 +1,238 @@ +package entrydb + +import ( + "bytes" + "errors" + "io" + "os" + "path/filepath" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestReadWrite(t *testing.T) { + t.Run("BasicReadWrite", func(t *testing.T) { + db := createEntryDB(t) + require.EqualValues(t, 0, db.Size()) + require.NoError(t, db.Append(createEntry(1))) + require.EqualValues(t, 1, db.Size()) + require.NoError(t, db.Append(createEntry(2))) + require.EqualValues(t, 2, db.Size()) + require.NoError(t, db.Append(createEntry(3))) + require.EqualValues(t, 3, db.Size()) + require.NoError(t, db.Append(createEntry(4))) + require.EqualValues(t, 4, db.Size()) + + requireRead(t, db, 0, createEntry(1)) + requireRead(t, db, 1, createEntry(2)) + requireRead(t, db, 2, createEntry(3)) + requireRead(t, db, 3, createEntry(4)) + + // Check we can read out of order + requireRead(t, db, 1, createEntry(2)) + }) + + t.Run("ReadPastEndOfFileReturnsEOF", func(t *testing.T) { + db := createEntryDB(t) + _, err := db.Read(0) + require.ErrorIs(t, err, io.EOF) + }) + + t.Run("WriteMultiple", func(t *testing.T) { + db := createEntryDB(t) + require.NoError(t, db.Append(createEntry(1), createEntry(2), createEntry(3))) + require.EqualValues(t, 3, db.Size()) + requireRead(t, db, 0, createEntry(1)) + requireRead(t, db, 1, createEntry(2)) + requireRead(t, db, 2, createEntry(3)) + }) +} + +func TestTruncate(t *testing.T) { + t.Run("Partial", func(t *testing.T) { + db := createEntryDB(t) + require.NoError(t, db.Append(createEntry(1))) + require.NoError(t, db.Append(createEntry(2))) + require.NoError(t, db.Append(createEntry(3))) + require.NoError(t, db.Append(createEntry(4))) + require.NoError(t, db.Append(createEntry(5))) + require.EqualValues(t, 5, db.Size()) + + require.NoError(t, db.Truncate(3)) + require.EqualValues(t, 4, db.Size()) // 0, 1, 2 and 3 are preserved + requireRead(t, db, 0, createEntry(1)) + requireRead(t, db, 1, createEntry(2)) + requireRead(t, db, 2, createEntry(3)) + + // 4 and 5 have been removed + _, err := db.Read(4) + require.ErrorIs(t, err, io.EOF) + _, err = db.Read(5) + require.ErrorIs(t, err, io.EOF) + }) + + t.Run("Complete", func(t *testing.T) { + db := createEntryDB(t) + require.NoError(t, db.Append(createEntry(1))) + require.NoError(t, db.Append(createEntry(2))) + require.NoError(t, db.Append(createEntry(3))) + require.EqualValues(t, 3, db.Size()) + + require.NoError(t, db.Truncate(-1)) + require.EqualValues(t, 0, db.Size()) // All items are removed + _, err := db.Read(0) + require.ErrorIs(t, err, io.EOF) + }) + + t.Run("AppendAfterTruncate", func(t *testing.T) { + db := createEntryDB(t) + require.NoError(t, db.Append(createEntry(1))) + require.NoError(t, db.Append(createEntry(2))) + require.NoError(t, db.Append(createEntry(3))) + require.EqualValues(t, 3, db.Size()) + + require.NoError(t, db.Truncate(1)) + require.EqualValues(t, 2, db.Size()) + newEntry := createEntry(4) + require.NoError(t, db.Append(newEntry)) + entry, err := db.Read(2) + require.NoError(t, err) + require.Equal(t, newEntry, entry) + }) +} + +func TestTruncateTrailingPartialEntries(t *testing.T) { + logger := testlog.Logger(t, log.LvlInfo) + file := filepath.Join(t.TempDir(), "entries.db") + entry1 := createEntry(1) + entry2 := createEntry(2) + invalidData := make([]byte, len(entry1)+len(entry2)+4) + copy(invalidData, entry1[:]) + copy(invalidData[EntrySize:], entry2[:]) + invalidData[len(invalidData)-1] = 3 // Some invalid trailing data + require.NoError(t, os.WriteFile(file, invalidData, 0o644)) + db, err := NewEntryDB(logger, file) + require.NoError(t, err) + defer db.Close() + + // Should automatically truncate the file to remove trailing partial entries + require.EqualValues(t, 2, db.Size()) + stat, err := os.Stat(file) + require.NoError(t, err) + require.EqualValues(t, 2*EntrySize, stat.Size()) +} + +func TestWriteErrors(t *testing.T) { + expectedErr := errors.New("some error") + + t.Run("TruncatePartiallyWrittenData", func(t *testing.T) { + db, stubData := createEntryDBWithStubData() + stubData.writeErr = expectedErr + stubData.writeErrAfterBytes = 3 + err := db.Append(createEntry(1), createEntry(2)) + require.ErrorIs(t, err, expectedErr) + + require.EqualValues(t, 0, db.Size(), "should not consider entries written") + require.Len(t, stubData.data, 0, "should truncate written bytes") + }) + + t.Run("FailBeforeDataWritten", func(t *testing.T) { + db, stubData := createEntryDBWithStubData() + stubData.writeErr = expectedErr + stubData.writeErrAfterBytes = 0 + err := db.Append(createEntry(1), createEntry(2)) + require.ErrorIs(t, err, expectedErr) + + require.EqualValues(t, 0, db.Size(), "should not consider entries written") + require.Len(t, stubData.data, 0, "no data written") + }) + + t.Run("PartialWriteAndTruncateFails", func(t *testing.T) { + db, stubData := createEntryDBWithStubData() + stubData.writeErr = expectedErr + stubData.writeErrAfterBytes = EntrySize + 2 + stubData.truncateErr = errors.New("boom") + err := db.Append(createEntry(1), createEntry(2)) + require.ErrorIs(t, err, expectedErr) + + require.EqualValues(t, 0, db.Size(), "should not consider entries written") + require.Len(t, stubData.data, stubData.writeErrAfterBytes, "rollback failed") + + _, err = db.Read(0) + require.ErrorIs(t, err, io.EOF, "should not have first entry") + _, err = db.Read(1) + require.ErrorIs(t, err, io.EOF, "should not have second entry") + + // Should retry truncate on next write + stubData.writeErr = nil + stubData.truncateErr = nil + err = db.Append(createEntry(3)) + require.NoError(t, err) + actual, err := db.Read(0) + require.NoError(t, err) + require.Equal(t, createEntry(3), actual) + }) +} + +func requireRead(t *testing.T, db *EntryDB, idx int64, expected Entry) { + actual, err := db.Read(idx) + require.NoError(t, err) + require.Equal(t, expected, actual) +} + +func createEntry(i byte) Entry { + return Entry(bytes.Repeat([]byte{i}, EntrySize)) +} + +func createEntryDB(t *testing.T) *EntryDB { + logger := testlog.Logger(t, log.LvlInfo) + db, err := NewEntryDB(logger, filepath.Join(t.TempDir(), "entries.db")) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, db.Close()) + }) + return db +} + +func createEntryDBWithStubData() (*EntryDB, *stubDataAccess) { + stubData := &stubDataAccess{} + db := &EntryDB{data: stubData, size: 0} + return db, stubData +} + +type stubDataAccess struct { + data []byte + writeErr error + writeErrAfterBytes int + truncateErr error +} + +func (s *stubDataAccess) ReadAt(p []byte, off int64) (n int, err error) { + return bytes.NewReader(s.data).ReadAt(p, off) +} + +func (s *stubDataAccess) Write(p []byte) (n int, err error) { + if s.writeErr != nil { + s.data = append(s.data, p[:s.writeErrAfterBytes]...) + return s.writeErrAfterBytes, s.writeErr + } + s.data = append(s.data, p...) + return len(p), nil +} + +func (s *stubDataAccess) Close() error { + return nil +} + +func (s *stubDataAccess) Truncate(size int64) error { + if s.truncateErr != nil { + return s.truncateErr + } + s.data = s.data[:size] + return nil +} + +var _ dataAccess = (*stubDataAccess)(nil)
diff --git OP/op-supervisor/supervisor/backend/db/iterator.go CELO/op-supervisor/supervisor/backend/db/iterator.go new file mode 100644 index 0000000000000000000000000000000000000000..da3047ee601a49b9771d780af2b5bf3f51e22410 --- /dev/null +++ CELO/op-supervisor/supervisor/backend/db/iterator.go @@ -0,0 +1,99 @@ +package db + +import ( + "errors" + "fmt" + "io" +) + +type iterator struct { + db *DB + nextEntryIdx int64 + + current logContext + hasExecMsg bool + + entriesRead int64 +} + +func (i *iterator) NextLog() (blockNum uint64, logIdx uint32, evtHash TruncatedHash, outErr error) { + for i.nextEntryIdx <= i.db.lastEntryIdx() { + entryIdx := i.nextEntryIdx + entry, err := i.db.store.Read(entryIdx) + if err != nil { + outErr = fmt.Errorf("failed to read entry %v: %w", i, err) + return + } + i.nextEntryIdx++ + i.entriesRead++ + i.hasExecMsg = false + switch entry[0] { + case typeSearchCheckpoint: + current, err := newSearchCheckpointFromEntry(entry) + if err != nil { + outErr = fmt.Errorf("failed to parse search checkpoint at idx %v: %w", entryIdx, err) + return + } + i.current.blockNum = current.blockNum + i.current.logIdx = current.logIdx + case typeInitiatingEvent: + evt, err := newInitiatingEventFromEntry(entry) + if err != nil { + outErr = fmt.Errorf("failed to parse initiating event at idx %v: %w", entryIdx, err) + return + } + i.current = evt.postContext(i.current) + blockNum = i.current.blockNum + logIdx = i.current.logIdx + evtHash = evt.logHash + i.hasExecMsg = evt.hasExecMsg + return + case typeCanonicalHash: // Skip + case typeExecutingCheck: // Skip + case typeExecutingLink: // Skip + default: + outErr = fmt.Errorf("unknown entry type at idx %v %v", entryIdx, entry[0]) + return + } + } + outErr = io.EOF + return +} + +func (i *iterator) ExecMessage() (ExecutingMessage, error) { + if !i.hasExecMsg { + return ExecutingMessage{}, nil + } + // Look ahead to find the exec message info + logEntryIdx := i.nextEntryIdx - 1 + execMsg, err := i.readExecMessage(logEntryIdx) + if err != nil { + return ExecutingMessage{}, fmt.Errorf("failed to read exec message for initiating event at %v: %w", logEntryIdx, err) + } + return execMsg, nil +} + +func (i *iterator) readExecMessage(initEntryIdx int64) (ExecutingMessage, error) { + linkIdx := initEntryIdx + 1 + if linkIdx%searchCheckpointFrequency == 0 { + linkIdx += 2 // skip the search checkpoint and canonical hash entries + } + linkEntry, err := i.db.store.Read(linkIdx) + if errors.Is(err, io.EOF) { + return ExecutingMessage{}, fmt.Errorf("%w: missing expected executing link event at idx %v", ErrDataCorruption, linkIdx) + } else if err != nil { + return ExecutingMessage{}, fmt.Errorf("failed to read executing link event at idx %v: %w", linkIdx, err) + } + + checkIdx := linkIdx + 1 + if checkIdx%searchCheckpointFrequency == 0 { + checkIdx += 2 // skip the search checkpoint and canonical hash entries + } + checkEntry, err := i.db.store.Read(checkIdx) + if errors.Is(err, io.EOF) { + return ExecutingMessage{}, fmt.Errorf("%w: missing expected executing check event at idx %v", ErrDataCorruption, checkIdx) + } else if err != nil { + return ExecutingMessage{}, fmt.Errorf("failed to read executing check event at idx %v: %w", checkIdx, err) + } + return newExecutingMessageFromEntries(linkEntry, checkEntry) +}
diff --git OP/op-supervisor/supervisor/backend/db/types.go CELO/op-supervisor/supervisor/backend/db/types.go new file mode 100644 index 0000000000000000000000000000000000000000..a9f9e50f6c07d41e055d436e11ecc2a17cb1aeec --- /dev/null +++ CELO/op-supervisor/supervisor/backend/db/types.go @@ -0,0 +1,29 @@ +package db + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" +) + +var ( + ErrLogOutOfOrder = errors.New("log out of order") + ErrDataCorruption = errors.New("data corruption") + ErrNotFound = errors.New("not found") +) + +type TruncatedHash [20]byte + +func TruncateHash(hash common.Hash) TruncatedHash { + var truncated TruncatedHash + copy(truncated[:], hash[0:20]) + return truncated +} + +type ExecutingMessage struct { + Chain uint32 + BlockNum uint64 + LogIdx uint32 + Timestamp uint64 + Hash TruncatedHash +}
diff --git OP/op-supervisor/supervisor/backend/mock.go CELO/op-supervisor/supervisor/backend/mock.go new file mode 100644 index 0000000000000000000000000000000000000000..8e5bc95632c26f0d35ea573e415b609370ea4251 --- /dev/null +++ CELO/op-supervisor/supervisor/backend/mock.go @@ -0,0 +1,52 @@ +package backend + +import ( + "context" + "errors" + "io" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type MockBackend struct { + started atomic.Bool +} + +var _ frontend.Backend = (*MockBackend)(nil) + +var _ io.Closer = (*MockBackend)(nil) + +func NewMockBackend() *MockBackend { + return &MockBackend{} +} + +func (m *MockBackend) Start(ctx context.Context) error { + if !m.started.CompareAndSwap(false, true) { + return errors.New("already started") + } + return nil +} + +func (m *MockBackend) Stop(ctx context.Context) error { + if !m.started.CompareAndSwap(true, false) { + return errors.New("already stopped") + } + return nil +} + +func (m *MockBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) { + return types.CrossUnsafe, nil +} + +func (m *MockBackend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) { + return types.CrossUnsafe, nil +} + +func (m *MockBackend) Close() error { + return nil +}
diff --git OP/op-supervisor/supervisor/entrypoint.go CELO/op-supervisor/supervisor/entrypoint.go new file mode 100644 index 0000000000000000000000000000000000000000..86befabb5da99e3e5a9985f685dc4e1d96d4a36b --- /dev/null +++ CELO/op-supervisor/supervisor/entrypoint.go @@ -0,0 +1,39 @@ +package supervisor + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/urfave/cli/v2" + + "github.com/ethereum/go-ethereum/log" + + opservice "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/cliapp" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-supervisor/flags" +) + +type MainFn func(ctx context.Context, cfg *config.Config, logger log.Logger) (cliapp.Lifecycle, error) + +// Main is the entrypoint into the Supervisor. +// This method returns a cliapp.LifecycleAction, to create an op-service CLI-lifecycle-managed supervisor with. +func Main(version string, fn MainFn) cliapp.LifecycleAction { + return func(cliCtx *cli.Context, closeApp context.CancelCauseFunc) (cliapp.Lifecycle, error) { + if err := flags.CheckRequired(cliCtx); err != nil { + return nil, err + } + cfg := flags.ConfigFromCLI(cliCtx, version) + if err := cfg.Check(); err != nil { + return nil, fmt.Errorf("invalid CLI flags: %w", err) + } + + l := oplog.NewLogger(oplog.AppOut(cliCtx), cfg.LogConfig) + oplog.SetGlobalLogHandler(l.Handler()) + opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l) + + l.Info("Initializing Supervisor") + return fn(cliCtx.Context, cfg, l) + } +}
diff --git OP/op-supervisor/supervisor/frontend/frontend.go CELO/op-supervisor/supervisor/frontend/frontend.go new file mode 100644 index 0000000000000000000000000000000000000000..9804b406914a7701e14d0467735f1112200097da --- /dev/null +++ CELO/op-supervisor/supervisor/frontend/frontend.go @@ -0,0 +1,54 @@ +package frontend + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type AdminBackend interface { + Start(ctx context.Context) error + Stop(ctx context.Context) error +} + +type QueryBackend interface { + CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) + CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) +} + +type Backend interface { + AdminBackend + QueryBackend +} + +type QueryFrontend struct { + Supervisor QueryBackend +} + +// CheckMessage checks the safety-level of an individual message. +// The payloadHash references the hash of the message-payload of the message. +func (q *QueryFrontend) CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) { + return q.Supervisor.CheckMessage(identifier, payloadHash) +} + +// CheckBlock checks the safety-level of an L2 block as a whole. +func (q *QueryFrontend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) { + return q.Supervisor.CheckBlock(chainID, blockHash, blockNumber) +} + +type AdminFrontend struct { + Supervisor Backend +} + +// Start starts the service, if it was previously stopped. +func (a *AdminFrontend) Start(ctx context.Context) error { + return a.Supervisor.Start(ctx) +} + +// Stop stops the service, if it was previously started. +func (a *AdminFrontend) Stop(ctx context.Context) error { + return a.Supervisor.Stop(ctx) +}
diff --git OP/op-supervisor/supervisor/service.go CELO/op-supervisor/supervisor/service.go new file mode 100644 index 0000000000000000000000000000000000000000..5b8b0fa14c1b1b4e61d350b1e2f7446fa6c6331c --- /dev/null +++ CELO/op-supervisor/supervisor/service.go @@ -0,0 +1,190 @@ +package supervisor + +import ( + "context" + "errors" + "fmt" + "io" + "sync/atomic" + + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-service/httputil" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-supervisor/metrics" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" +) + +type Backend interface { + frontend.Backend + io.Closer +} + +// SupervisorService implements the full-environment bells and whistles around the Supervisor. +// This includes the setup and teardown of metrics, pprof, admin RPC, regular RPC etc. +type SupervisorService struct { + closing atomic.Bool + + log log.Logger + + metrics metrics.Metricer + + backend Backend + + pprofService *oppprof.Service + metricsSrv *httputil.HTTPServer + rpcServer *oprpc.Server +} + +var _ cliapp.Lifecycle = (*SupervisorService)(nil) + +func SupervisorFromConfig(ctx context.Context, cfg *config.Config, logger log.Logger) (*SupervisorService, error) { + su := &SupervisorService{log: logger} + if err := su.initFromCLIConfig(ctx, cfg); err != nil { + return nil, errors.Join(err, su.Stop(ctx)) // try to clean up our failed initialization attempt + } + return su, nil +} + +func (su *SupervisorService) initFromCLIConfig(ctx context.Context, cfg *config.Config) error { + su.initMetrics(cfg) + if err := su.initPProf(cfg); err != nil { + return fmt.Errorf("failed to start PProf server: %w", err) + } + if err := su.initMetricsServer(cfg); err != nil { + return fmt.Errorf("failed to start Metrics server: %w", err) + } + su.initBackend(cfg) + if err := su.initRPCServer(cfg); err != nil { + return fmt.Errorf("failed to start RPC server: %w", err) + } + return nil +} + +func (su *SupervisorService) initBackend(cfg *config.Config) { + if cfg.MockRun { + su.backend = backend.NewMockBackend() + } else { + su.backend = backend.NewSupervisorBackend() + } +} + +func (su *SupervisorService) initMetrics(cfg *config.Config) { + if cfg.MetricsConfig.Enabled { + procName := "default" + su.metrics = metrics.NewMetrics(procName) + su.metrics.RecordInfo(cfg.Version) + } else { + su.metrics = metrics.NoopMetrics + } +} + +func (su *SupervisorService) initPProf(cfg *config.Config) error { + su.pprofService = oppprof.New( + cfg.PprofConfig.ListenEnabled, + cfg.PprofConfig.ListenAddr, + cfg.PprofConfig.ListenPort, + cfg.PprofConfig.ProfileType, + cfg.PprofConfig.ProfileDir, + cfg.PprofConfig.ProfileFilename, + ) + + if err := su.pprofService.Start(); err != nil { + return fmt.Errorf("failed to start pprof service: %w", err) + } + + return nil +} + +func (su *SupervisorService) initMetricsServer(cfg *config.Config) error { + if !cfg.MetricsConfig.Enabled { + su.log.Info("Metrics disabled") + return nil + } + m, ok := su.metrics.(opmetrics.RegistryMetricer) + if !ok { + return fmt.Errorf("metrics were enabled, but metricer %T does not expose registry for metrics-server", su.metrics) + } + su.log.Debug("Starting metrics server", "addr", cfg.MetricsConfig.ListenAddr, "port", cfg.MetricsConfig.ListenPort) + metricsSrv, err := opmetrics.StartServer(m.Registry(), cfg.MetricsConfig.ListenAddr, cfg.MetricsConfig.ListenPort) + if err != nil { + return fmt.Errorf("failed to start metrics server: %w", err) + } + su.log.Info("Started metrics server", "addr", metricsSrv.Addr()) + su.metricsSrv = metricsSrv + return nil +} + +func (su *SupervisorService) initRPCServer(cfg *config.Config) error { + server := oprpc.NewServer( + cfg.RPC.ListenAddr, + cfg.RPC.ListenPort, + cfg.Version, + oprpc.WithLogger(su.log), + //oprpc.WithHTTPRecorder(su.metrics), // TODO(protocol-quest#286) hook up metrics to RPC server + ) + if cfg.RPC.EnableAdmin { + su.log.Info("Admin RPC enabled") + server.AddAPI(rpc.API{ + Namespace: "admin", + Service: &frontend.AdminFrontend{Supervisor: su.backend}, + Authenticated: true, // TODO(protocol-quest#286): enforce auth on this or not? + }) + } + server.AddAPI(rpc.API{ + Namespace: "supervisor", + Service: &frontend.QueryFrontend{Supervisor: su.backend}, + Authenticated: false, + }) + su.rpcServer = server + return nil +} + +func (su *SupervisorService) Start(ctx context.Context) error { + su.log.Info("Starting JSON-RPC server") + if err := su.rpcServer.Start(); err != nil { + return fmt.Errorf("unable to start RPC server: %w", err) + } + + su.metrics.RecordUp() + return nil +} + +func (su *SupervisorService) Stop(ctx context.Context) error { + if !su.closing.CompareAndSwap(false, true) { + return nil // already closing + } + + var result error + if su.rpcServer != nil { + if err := su.rpcServer.Stop(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to stop RPC server: %w", err)) + } + } + if su.backend != nil { + if err := su.backend.Close(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to close supervisor backend: %w", err)) + } + } + if su.pprofService != nil { + if err := su.pprofService.Stop(ctx); err != nil { + result = errors.Join(result, fmt.Errorf("failed to stop PProf server: %w", err)) + } + } + if su.metricsSrv != nil { + if err := su.metricsSrv.Stop(ctx); err != nil { + result = errors.Join(result, fmt.Errorf("failed to stop metrics server: %w", err)) + } + } + return result +} + +func (su *SupervisorService) Stopped() bool { + return su.closing.Load() +}
diff --git OP/op-supervisor/supervisor/service_test.go CELO/op-supervisor/supervisor/service_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8cc4dcfa66786f0cf4b86103c057e05bf0174039 --- /dev/null +++ CELO/op-supervisor/supervisor/service_test.go @@ -0,0 +1,73 @@ +package supervisor + +import ( + "context" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/dial" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func TestSupervisorService(t *testing.T) { + cfg := &config.Config{ + Version: "", + LogConfig: oplog.CLIConfig{ + Level: log.LevelError, + Color: false, + Format: oplog.FormatLogFmt, + }, + MetricsConfig: opmetrics.CLIConfig{ + Enabled: true, + ListenAddr: "127.0.0.1", + ListenPort: 0, // pick a port automatically + }, + PprofConfig: oppprof.CLIConfig{ + ListenEnabled: true, + ListenAddr: "127.0.0.1", + ListenPort: 0, // pick a port automatically + ProfileType: "", + ProfileDir: "", + ProfileFilename: "", + }, + RPC: oprpc.CLIConfig{ + ListenAddr: "127.0.0.1", + ListenPort: 0, // pick a port automatically + EnableAdmin: true, + }, + MockRun: true, + } + logger := testlog.Logger(t, log.LevelError) + supervisor, err := SupervisorFromConfig(context.Background(), cfg, logger) + require.NoError(t, err) + require.NoError(t, supervisor.Start(context.Background()), "start service") + // run some RPC tests against the service with the mock backend + { + endpoint := "http://" + supervisor.rpcServer.Endpoint() + t.Logf("dialing %s", endpoint) + cl, err := dial.DialRPCClientWithTimeout(context.Background(), time.Second*5, logger, endpoint) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + var dest types.SafetyLevel + err = cl.CallContext(ctx, &dest, "supervisor_checkBlock", + (*hexutil.U256)(uint256.NewInt(1)), common.Hash{0xab}, hexutil.Uint64(123)) + cancel() + require.NoError(t, err) + require.Equal(t, types.CrossUnsafe, dest, "expecting mock to return cross-unsafe") + cl.Close() + } + require.NoError(t, supervisor.Stop(context.Background()), "stop service") +}
diff --git OP/op-supervisor/supervisor/types/types.go CELO/op-supervisor/supervisor/types/types.go new file mode 100644 index 0000000000000000000000000000000000000000..288cdc5166b8ed7eb03f8e6340dec1462a0d1868 --- /dev/null +++ CELO/op-supervisor/supervisor/types/types.go @@ -0,0 +1,89 @@ +package types + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +type Identifier struct { + Origin common.Address + BlockNumber uint64 + LogIndex uint64 + Timestamp uint64 + ChainID uint256.Int // flat, not a pointer, to make Identifier safe as map key +} + +type identifierMarshaling struct { + Origin common.Address `json:"origin"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + LogIndex hexutil.Uint64 `json:"logIndex"` + Timestamp hexutil.Uint64 `json:"timestamp"` + ChainID hexutil.U256 `json:"chainID"` +} + +func (id Identifier) MarshalJSON() ([]byte, error) { + var enc identifierMarshaling + enc.Origin = id.Origin + enc.BlockNumber = hexutil.Uint64(id.BlockNumber) + enc.LogIndex = hexutil.Uint64(id.LogIndex) + enc.Timestamp = hexutil.Uint64(id.Timestamp) + enc.ChainID = (hexutil.U256)(id.ChainID) + return json.Marshal(&enc) +} + +func (id *Identifier) UnmarshalJSON(input []byte) error { + var dec identifierMarshaling + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + id.Origin = dec.Origin + id.BlockNumber = uint64(dec.BlockNumber) + id.LogIndex = uint64(dec.LogIndex) + id.Timestamp = uint64(dec.Timestamp) + id.ChainID = (uint256.Int)(dec.ChainID) + return nil +} + +type SafetyLevel string + +func (lvl SafetyLevel) String() string { + return string(lvl) +} + +func (lvl SafetyLevel) Valid() bool { + switch lvl { + case Finalized, Safe, CrossUnsafe, Unsafe: + return true + default: + return false + } +} + +func (lvl SafetyLevel) MarshalText() ([]byte, error) { + return []byte(lvl), nil +} + +func (lvl *SafetyLevel) UnmarshalText(text []byte) error { + if lvl == nil { + return errors.New("cannot unmarshal into nil SafetyLevel") + } + x := SafetyLevel(text) + if !x.Valid() { + return fmt.Errorf("unrecognized safety level: %q", text) + } + *lvl = x + return nil +} + +const ( + Finalized SafetyLevel = "finalized" + Safe SafetyLevel = "safe" + CrossUnsafe SafetyLevel = "cross-unsafe" + Unsafe SafetyLevel = "unsafe" +)
diff --git OP/op-supervisor/supervisor/types/types_test.go CELO/op-supervisor/supervisor/types/types_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c04ad00d4f27c74e7c3659249d60ad3bb0cbef7d --- /dev/null +++ CELO/op-supervisor/supervisor/types/types_test.go @@ -0,0 +1,40 @@ +package types + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +func FuzzRoundtripIdentifierJSONMarshal(f *testing.F) { + f.Fuzz(func(t *testing.T, origin []byte, blockNumber uint64, logIndex uint64, timestamp uint64, chainID []byte) { + if len(chainID) > 32 { + chainID = chainID[:32] + } + + id := Identifier{ + Origin: common.BytesToAddress(origin), + BlockNumber: blockNumber, + LogIndex: logIndex, + Timestamp: timestamp, + ChainID: uint256.Int{}, + } + id.ChainID.SetBytes(chainID) + + raw, err := json.Marshal(&id) + require.NoError(t, err) + + var dec Identifier + require.NoError(t, json.Unmarshal(raw, &dec)) + + require.Equal(t, id.Origin, dec.Origin) + require.Equal(t, id.BlockNumber, dec.BlockNumber) + require.Equal(t, id.LogIndex, dec.LogIndex) + require.Equal(t, id.Timestamp, dec.Timestamp) + require.Equal(t, id.ChainID, dec.ChainID) + }) +}
diff --git OP/ops/check-changed/requirements.txt CELO/ops/check-changed/requirements.txt index c58d1db7352bf47685cfb95e29bdad80919dca2a..b5028c7faf855bf73a77c3201ee1fc814327ce22 100644 --- OP/ops/check-changed/requirements.txt +++ CELO/ops/check-changed/requirements.txt @@ -8,5 +8,5 @@ PyGithub==1.57 PyJWT==2.6.0 PyNaCl==1.5.0 requests==2.32.0 -urllib3==1.26.18 +urllib3==1.26.19 wrapt==1.14.1
diff --git OP/ops/docker/Dockerfile.packages CELO/ops/docker/Dockerfile.packages index c61b533e18355ed73f0d746f3c8d5198d9a68d7b..224a748fc56c4855f5bdd6e6dc257b0fbe65c6b5 100644 --- OP/ops/docker/Dockerfile.packages +++ CELO/ops/docker/Dockerfile.packages @@ -98,10 +98,6 @@ FROM base as balance-mon WORKDIR /opt/optimism/packages/chain-mon/internal CMD ["start:balance-mon"]   -FROM base as drippie-mon -WORKDIR /opt/optimism/packages/chain-mon/contrib -CMD ["start:drippie-mon"] - from base as fault-mon WORKDIR /opt/optimism/packages/chain-mon/ CMD ["start:fault-mon"]
diff --git OP/ops/docker/op-stack-go/Dockerfile CELO/ops/docker/op-stack-go/Dockerfile index 07104ed41caf2d1094c976288e7f2d01aa14675b..848d5ecc720c20e53e119d2c08dd83e635693250 100644 --- OP/ops/docker/op-stack-go/Dockerfile +++ CELO/ops/docker/op-stack-go/Dockerfile @@ -55,58 +55,63 @@ # "sharing" defaults to "shared", the cache will thus be available to other concurrent docker builds. FROM --platform=$BUILDPLATFORM builder as cannon-builder ARG CANNON_VERSION=v0.0.0 RUN --mount=type=cache,target=/root/.cache/go-build cd cannon && make cannon \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-program-builder ARG OP_PROGRAM_VERSION=v0.0.0 # note: we only build the host, that's all the user needs. No Go MIPS cross-build in docker RUN --mount=type=cache,target=/root/.cache/go-build cd op-program && make op-program-host \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROGRAM_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROGRAM_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-heartbeat-builder ARG OP_HEARTBEAT_VERSION=v0.0.0 RUN --mount=type=cache,target=/root/.cache/go-build cd op-heartbeat && make op-heartbeat \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_HEARTBEAT_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_HEARTBEAT_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-wheel-builder ARG OP_WHEEL_VERSION=v0.0.0 RUN --mount=type=cache,target=/root/.cache/go-build cd op-wheel && make op-wheel \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_WHEEL_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_WHEEL_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-node-builder ARG OP_NODE_VERSION=v0.0.0 RUN echo TARGETOS=$TARGETOS TARGETARCH=$TARGETARCH BUILDPLATFORM=$BUILDPLATFORM TARGETPLATFORM=$TARGETPLATFORM OP_NODE_VERSION=$OP_NODE_VERSION RUN --mount=type=cache,target=/root/.cache/go-build cd op-node && make op-node \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_NODE_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_NODE_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-challenger-builder ARG OP_CHALLENGER_VERSION=v0.0.0 RUN --mount=type=cache,target=/root/.cache/go-build cd op-challenger && make op-challenger \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_CHALLENGER_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_CHALLENGER_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-dispute-mon-builder ARG OP_DISPUTE_MON_VERSION=v0.0.0 RUN --mount=type=cache,target=/root/.cache/go-build cd op-dispute-mon && make op-dispute-mon \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_DISPUTE_MON_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_DISPUTE_MON_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-batcher-builder ARG OP_BATCHER_VERSION=v0.0.0 RUN --mount=type=cache,target=/root/.cache/go-build cd op-batcher && make op-batcher \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_BATCHER_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_BATCHER_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-proposer-builder ARG OP_PROPOSER_VERSION=v0.0.0 RUN --mount=type=cache,target=/root/.cache/go-build cd op-proposer && make op-proposer \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROPOSER_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROPOSER_VERSION"   FROM --platform=$BUILDPLATFORM builder as op-conductor-builder ARG OP_CONDUCTOR_VERSION=v0.0.0 RUN --mount=type=cache,target=/root/.cache/go-build cd op-conductor && make op-conductor \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_CONDUCTOR_VERSION" + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_CONDUCTOR_VERSION"   FROM --platform=$BUILDPLATFORM builder as da-server-builder RUN --mount=type=cache,target=/root/.cache/go-build cd op-plasma && make da-server \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE + +FROM --platform=$BUILDPLATFORM builder as op-supervisor-builder +ARG OP_SUPERVISOR_VERSION=v0.0.0 +RUN --mount=type=cache,target=/root/.cache/go-build cd op-supervisor && make op-supervisor \ + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_SUPERVISOR_VERSION"   FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE as cannon-target COPY --from=cannon-builder /app/cannon/bin/cannon /usr/local/bin/ @@ -157,3 +162,7 @@ FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE as da-server-target COPY --from=da-server-builder /app/op-plasma/bin/da-server /usr/local/bin/ CMD ["da-server"] + +FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE as op-supervisor-target +COPY --from=op-supervisor-builder /app/op-supervisor/bin/op-supervisor /usr/local/bin/ +ENTRYPOINT ["op-supervisor"]
diff --git OP/ops/docker/op-stack-go/Dockerfile.dockerignore CELO/ops/docker/op-stack-go/Dockerfile.dockerignore index 9512fe9fbc7c66b971c7118aedb3d481472cbf3e..e012262ad78ab0adaa6a2a66fc4f289e5fd53960 100644 --- OP/ops/docker/op-stack-go/Dockerfile.dockerignore +++ CELO/ops/docker/op-stack-go/Dockerfile.dockerignore @@ -17,6 +17,7 @@ !/op-preimage !/op-program !/op-proposer !/op-service +!/op-supervisor !/op-wheel !/op-plasma !/go.mod
diff --git OP/ops/scripts/ci-docker-tag-op-stack-release.sh CELO/ops/scripts/ci-docker-tag-op-stack-release.sh index a8d7e9522c0e6754cf7db25f720e913ff6178963..f743a20cbc41c5f44d34d7c3738193081fec79f4 100755 --- OP/ops/scripts/ci-docker-tag-op-stack-release.sh +++ CELO/ops/scripts/ci-docker-tag-op-stack-release.sh @@ -6,7 +6,7 @@ DOCKER_REPO=$1 GIT_TAG=$2 GIT_SHA=$3   -IMAGE_NAME=$(echo "$GIT_TAG" | grep -Eow '^(ci-builder(-rust)?|chain-mon|proxyd|da-server|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)' || true) +IMAGE_NAME=$(echo "$GIT_TAG" | grep -Eow '^(ci-builder(-rust)?|chain-mon|da-server|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)' || true) if [ -z "$IMAGE_NAME" ]; then echo "image name could not be parsed from git tag '$GIT_TAG'" exit 1
diff --git OP/ops/tag-service/tag-service.py CELO/ops/tag-service/tag-service.py index 2db9e36c53b56dec12e0b2b0cd3309a708921df4..319c997eb8586f82736743edd6ff18825fac4309 100755 --- OP/ops/tag-service/tag-service.py +++ CELO/ops/tag-service/tag-service.py @@ -3,7 +3,6 @@ import logging.config import os import re import subprocess -import sys   import click import semver @@ -13,13 +12,13 @@ MIN_VERSIONS = { 'ci-builder': '0.6.0', 'ci-builder-rust': '0.1.0', 'chain-mon': '0.2.2', + 'da-server': '0.0.4', 'op-node': '0.10.14', 'op-batcher': '0.10.14', 'op-challenger': '0.0.4', 'op-program': '0.0.0', 'op-dispute-mon': '0.0.0', 'op-proposer': '0.10.14', - 'proxyd': '3.16.0', 'op-heartbeat': '0.1.0', 'op-contracts': '1.0.0', 'op-conductor': '0.0.0',
diff --git OP/ops/tag-service/tag-tool.py CELO/ops/tag-service/tag-tool.py index f3fd847ce069da4d96a34b3e5b86729be9af7aed..5da97d3cb924ea89863f1a63e9205063eddc6e71 100644 --- OP/ops/tag-service/tag-tool.py +++ CELO/ops/tag-service/tag-tool.py @@ -1,6 +1,5 @@ import argparse import subprocess -import re import semver   SERVICES = [ @@ -13,7 +12,6 @@ 'op-challenger', 'op-dispute-mon', 'op-proposer', 'da-server', - 'proxyd', 'op-heartbeat', 'op-contracts', 'test',
diff --git OP/packages/chain-mon/contrib/drippie-mon/service.ts CELO/packages/chain-mon/contrib/drippie-mon/service.ts deleted file mode 100644 index 3fc70181872d7fa2f6f92e8c9b35c8abe28a2016..0000000000000000000000000000000000000000 --- OP/packages/chain-mon/contrib/drippie-mon/service.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { - BaseServiceV2, - StandardOptions, - Gauge, - Counter, - validators, -} from '@eth-optimism/common-ts' -import { Provider } from '@ethersproject/abstract-provider' -import { ethers } from 'ethers' -import * as DrippieArtifact from '@eth-optimism/contracts-bedrock/forge-artifacts/Drippie.sol/Drippie.json' - -import { version } from '../../package.json' - -type DrippieMonOptions = { - rpc: Provider - drippieAddress: string -} - -type DrippieMonMetrics = { - isExecutable: Gauge - executedDripCount: Gauge - unexpectedRpcErrors: Counter -} - -type DrippieMonState = { - drippie: ethers.Contract -} - -export class DrippieMonService extends BaseServiceV2< - DrippieMonOptions, - DrippieMonMetrics, - DrippieMonState -> { - constructor(options?: Partial<DrippieMonOptions & StandardOptions>) { - super({ - version, - name: 'drippie-mon', - loop: true, - options: { - loopIntervalMs: 60_000, - ...options, - }, - optionsSpec: { - rpc: { - validator: validators.provider, - desc: 'Provider for network where Drippie is deployed', - }, - drippieAddress: { - validator: validators.str, - desc: 'Address of Drippie contract', - public: true, - }, - }, - metricsSpec: { - isExecutable: { - type: Gauge, - desc: 'Whether or not the drip is currently executable', - labels: ['name'], - }, - executedDripCount: { - type: Gauge, - desc: 'Number of times a drip has been executed', - labels: ['name'], - }, - unexpectedRpcErrors: { - type: Counter, - desc: 'Number of unexpected RPC errors', - labels: ['section', 'name'], - }, - }, - }) - } - - protected async init(): Promise<void> { - this.state.drippie = new ethers.Contract( - this.options.drippieAddress, - DrippieArtifact.abi, - this.options.rpc - ) - } - - protected async main(): Promise<void> { - let dripCreatedEvents: ethers.Event[] - try { - dripCreatedEvents = await this.state.drippie.queryFilter( - this.state.drippie.filters.DripCreated() - ) - } catch (err) { - this.logger.info(`got unexpected RPC error`, { - section: 'creations', - name: 'NULL', - err, - }) - - this.metrics.unexpectedRpcErrors.inc({ - section: 'creations', - name: 'NULL', - }) - - return - } - - // Not the most efficient thing in the world. Will end up making one request for every drip - // created. We don't expect there to be many drips, so this is fine for now. We can also cache - // and skip any archived drips to cut down on a few requests. Worth keeping an eye on this to - // see if it's a bottleneck. - for (const event of dripCreatedEvents) { - const name = event.args.name - - let drip: any - try { - drip = await this.state.drippie.drips(name) - } catch (err) { - this.logger.info(`got unexpected RPC error`, { - section: 'drips', - name, - err, - }) - - this.metrics.unexpectedRpcErrors.inc({ - section: 'drips', - name, - }) - - continue - } - - this.logger.info(`getting drip executable status`, { - name, - count: drip.count.toNumber(), - }) - - this.metrics.executedDripCount.set( - { - name, - }, - drip.count.toNumber() - ) - - let executable: boolean - try { - // To avoid making unnecessary RPC requests, filter out any drips that we don't expect to - // be executable right now. Only active drips (status = 2) and drips that are due to be - // executed are expected to be executable (but might not be based on the dripcheck). - if ( - drip.status === 2 && - drip.last.toNumber() + drip.config.interval.toNumber() < - Date.now() / 1000 - ) { - executable = await this.state.drippie.executable(name) - } else { - executable = false - } - } catch (err) { - // All reverts include the string "Drippie:", so we can check for that. - if (err.message.includes('Drippie:')) { - // Not executable yet. - executable = false - } else { - this.logger.info(`got unexpected RPC error`, { - section: 'executable', - name, - err, - }) - - this.metrics.unexpectedRpcErrors.inc({ - section: 'executable', - name, - }) - - continue - } - } - - this.logger.info(`got drip executable status`, { - name, - executable, - }) - - this.metrics.isExecutable.set( - { - name, - }, - executable ? 1 : 0 - ) - } - } -} - -if (require.main === module) { - const service = new DrippieMonService() - service.run() -}
diff --git OP/packages/chain-mon/src/index.ts CELO/packages/chain-mon/src/index.ts index 9f838b714b40448a09c33157b7eead2dc2e805e0..a322a2e0019fd4f7d5b20776111228d21b1acca0 100644 --- OP/packages/chain-mon/src/index.ts +++ CELO/packages/chain-mon/src/index.ts @@ -1,5 +1,4 @@ export * from '../internal/balance-mon/service' -export * from '../contrib/drippie-mon/service' export * from './fault-mon/index' export * from '../internal/multisig-mon/service' export * from './wd-mon/service'
diff --git OP/packages/contracts-bedrock/lib/multicall/src/Multicall3.sol CELO/packages/contracts-bedrock/lib/multicall/src/Multicall3.sol new file mode 100644 index 0000000000000000000000000000000000000000..92195a5e91b974b51d163cdf29f56f02f2b149c9 --- /dev/null +++ CELO/packages/contracts-bedrock/lib/multicall/src/Multicall3.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +/// @title Multicall3 +/// @notice Aggregate results from multiple function calls +/// @dev Multicall & Multicall2 backwards-compatible +/// @dev Aggregate methods are marked `payable` to save 24 gas per call +/// @author Michael Elliot <mike@makerdao.com> +/// @author Joshua Levine <joshua@makerdao.com> +/// @author Nick Johnson <arachnid@notdot.net> +/// @author Andreas Bigger <andreas@nascent.xyz> +/// @author Matt Solomon <matt@mattsolomon.dev> +contract Multicall3 { + struct Call { + address target; + bytes callData; + } + + struct Call3 { + address target; + bool allowFailure; + bytes callData; + } + + struct Call3Value { + address target; + bool allowFailure; + uint256 value; + bytes callData; + } + + struct Result { + bool success; + bytes returnData; + } + + /// @notice Backwards-compatible call aggregation with Multicall + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return returnData An array of bytes containing the responses + function aggregate( + Call[] calldata calls + ) public payable returns (uint256 blockNumber, bytes[] memory returnData) { + blockNumber = block.number; + uint256 length = calls.length; + returnData = new bytes[](length); + Call calldata call; + for (uint256 i = 0; i < length; ) { + bool success; + call = calls[i]; + (success, returnData[i]) = call.target.call(call.callData); + require(success, 'Multicall3: call failed'); + unchecked { + ++i; + } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls without requiring success + /// @param requireSuccess If true, require all calls to succeed + /// @param calls An array of Call structs + /// @return returnData An array of Result structs + function tryAggregate( + bool requireSuccess, + Call[] calldata calls + ) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata call; + for (uint256 i = 0; i < length; ) { + Result memory result = returnData[i]; + call = calls[i]; + (result.success, result.returnData) = call.target.call(call.callData); + if (requireSuccess) require(result.success, 'Multicall3: call failed'); + unchecked { + ++i; + } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function tryBlockAndAggregate( + bool requireSuccess, + Call[] calldata calls + ) + public + payable + returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) + { + blockNumber = block.number; + blockHash = blockhash(block.number); + returnData = tryAggregate(requireSuccess, calls); + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function blockAndAggregate( + Call[] calldata calls + ) + public + payable + returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) + { + (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); + } + + /// @notice Aggregate calls, ensuring each returns success if required + /// @param calls An array of Call3 structs + /// @return returnData An array of Result structs + function aggregate3( + Call3[] calldata calls + ) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call3 calldata calli; + for (uint256 i = 0; i < length; ) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore( + 0x00, + 0x08c379a000000000000000000000000000000000000000000000000000000000 + ) + // set data offset + mstore( + 0x04, + 0x0000000000000000000000000000000000000000000000000000000000000020 + ) + // set length of revert string + mstore( + 0x24, + 0x0000000000000000000000000000000000000000000000000000000000000017 + ) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore( + 0x44, + 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000 + ) + revert(0x00, 0x64) + } + } + unchecked { + ++i; + } + } + } + + /// @notice Aggregate calls with a msg value + /// @notice Reverts if msg.value is less than the sum of the call values + /// @param calls An array of Call3Value structs + /// @return returnData An array of Result structs + function aggregate3Value( + Call3Value[] calldata calls + ) public payable returns (Result[] memory returnData) { + uint256 valAccumulator; + uint256 length = calls.length; + returnData = new Result[](length); + Call3Value calldata calli; + for (uint256 i = 0; i < length; ) { + Result memory result = returnData[i]; + calli = calls[i]; + uint256 val = calli.value; + // Humanity will be a Type V Kardashev Civilization before this overflows - andreas + // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 + unchecked { + valAccumulator += val; + } + (result.success, result.returnData) = calli.target.call{value: val}( + calli.callData + ); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore( + 0x00, + 0x08c379a000000000000000000000000000000000000000000000000000000000 + ) + // set data offset + mstore( + 0x04, + 0x0000000000000000000000000000000000000000000000000000000000000020 + ) + // set length of revert string + mstore( + 0x24, + 0x0000000000000000000000000000000000000000000000000000000000000017 + ) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore( + 0x44, + 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000 + ) + revert(0x00, 0x84) + } + } + unchecked { + ++i; + } + } + // Finally, make sure the msg.value = SUM(call[0...i].value) + require(msg.value == valAccumulator, 'Multicall3: value mismatch'); + } + + /// @notice Returns the block hash for the given block number + /// @param blockNumber The block number + function getBlockHash( + uint256 blockNumber + ) public view returns (bytes32 blockHash) { + blockHash = blockhash(blockNumber); + } + + /// @notice Returns the block number + function getBlockNumber() public view returns (uint256 blockNumber) { + blockNumber = block.number; + } + + /// @notice Returns the block coinbase + function getCurrentBlockCoinbase() public view returns (address coinbase) { + coinbase = block.coinbase; + } + + /// @notice Returns the block difficulty + function getCurrentBlockDifficulty() + public + view + returns (uint256 difficulty) + { + difficulty = block.difficulty; + } + + /// @notice Returns the block gas limit + function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { + gaslimit = block.gaslimit; + } + + /// @notice Returns the block timestamp + function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { + timestamp = block.timestamp; + } + + /// @notice Returns the (ETH) balance of a given address + function getEthBalance(address addr) public view returns (uint256 balance) { + balance = addr.balance; + } + + /// @notice Returns the block hash of the last block + function getLastBlockHash() public view returns (bytes32 blockHash) { + unchecked { + blockHash = blockhash(block.number - 1); + } + } + + /// @notice Gets the base fee of the given block + /// @notice Can revert if the BASEFEE opcode is not implemented by the given chain + function getBasefee() public view returns (uint256 basefee) { + basefee = block.basefee; + } + + /// @notice Returns the chain id + function getChainId() public view returns (uint256 chainid) { + chainid = block.chainid; + } +}
diff --git OP/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol CELO/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol index bf2f5bcd165a37d387306e517fcdc43c5b1c2c75..b90c5ea7371b0fd70ec560f142114efa961fb2d7 100644 --- OP/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol +++ CELO/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol @@ -1,265 +1,487 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15;   -/// @notice Execute an instruction. -function executeMipsInstruction(uint32 insn, uint32 rs, uint32 rt, uint32 mem) pure returns (uint32 out) { - unchecked { - uint32 opcode = insn >> 26; // 6-bits +import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";   - if (opcode == 0 || (opcode >= 8 && opcode < 0xF)) { - uint32 func = insn & 0x3f; // 6-bits - assembly { - // transform ArithLogI to SPECIAL - switch opcode - // addi - case 0x8 { func := 0x20 } - // addiu - case 0x9 { func := 0x21 } - // stli - case 0xA { func := 0x2A } +library MIPSInstructions { + /// @notice Execute an instruction. + function executeMipsInstruction( + uint32 _insn, + uint32 _rs, + uint32 _rt, + uint32 _mem + ) + internal + pure + returns (uint32 out_) + { + unchecked { + uint32 opcode = _insn >> 26; // 6-bits + + if (opcode == 0 || (opcode >= 8 && opcode < 0xF)) { + uint32 func = _insn & 0x3f; // 6-bits + assembly { + // transform ArithLogI to SPECIAL + switch opcode + // addi + case 0x8 { func := 0x20 } + // addiu + case 0x9 { func := 0x21 } + // stli + case 0xA { func := 0x2A } + // sltiu + case 0xB { func := 0x2B } + // andi + case 0xC { func := 0x24 } + // ori + case 0xD { func := 0x25 } + // xori + case 0xE { func := 0x26 } + } + + // sll + if (func == 0x00) { + return _rt << ((_insn >> 6) & 0x1F); + } + // srl + else if (func == 0x02) { + return _rt >> ((_insn >> 6) & 0x1F); + } + // sra + else if (func == 0x03) { + uint32 shamt = (_insn >> 6) & 0x1F; + return signExtend(_rt >> shamt, 32 - shamt); + } + // sllv + else if (func == 0x04) { + return _rt << (_rs & 0x1F); + } + // srlv + else if (func == 0x6) { + return _rt >> (_rs & 0x1F); + } + // srav + else if (func == 0x07) { + return signExtend(_rt >> _rs, 32 - _rs); + } + // functs in range [0x8, 0x1b] are handled specially by other functions + // Explicitly enumerate each funct in range to reduce code diff against Go Vm + // jr + else if (func == 0x08) { + return _rs; + } + // jalr + else if (func == 0x09) { + return _rs; + } + // movz + else if (func == 0x0a) { + return _rs; + } + // movn + else if (func == 0x0b) { + return _rs; + } + // syscall + else if (func == 0x0c) { + return _rs; + } + // 0x0d - break not supported + // sync + else if (func == 0x0f) { + return _rs; + } + // mfhi + else if (func == 0x10) { + return _rs; + } + // mthi + else if (func == 0x11) { + return _rs; + } + // mflo + else if (func == 0x12) { + return _rs; + } + // mtlo + else if (func == 0x13) { + return _rs; + } + // mult + else if (func == 0x18) { + return _rs; + } + // multu + else if (func == 0x19) { + return _rs; + } + // div + else if (func == 0x1a) { + return _rs; + } + // divu + else if (func == 0x1b) { + return _rs; + } + // The rest includes transformed R-type arith imm instructions + // add + else if (func == 0x20) { + return (_rs + _rt); + } + // addu + else if (func == 0x21) { + return (_rs + _rt); + } + // sub + else if (func == 0x22) { + return (_rs - _rt); + } + // subu + else if (func == 0x23) { + return (_rs - _rt); + } + // and + else if (func == 0x24) { + return (_rs & _rt); + } + // or + else if (func == 0x25) { + return (_rs | _rt); + } + // xor + else if (func == 0x26) { + return (_rs ^ _rt); + } + // nor + else if (func == 0x27) { + return ~(_rs | _rt); + } + // slti + else if (func == 0x2a) { + return int32(_rs) < int32(_rt) ? 1 : 0; + } // sltiu - case 0xB { func := 0x2B } - // andi - case 0xC { func := 0x24 } - // ori - case 0xD { func := 0x25 } - // xori - case 0xE { func := 0x26 } + else if (func == 0x2b) { + return _rs < _rt ? 1 : 0; + } else { + revert("invalid instruction"); + } + } else { + // SPECIAL2 + if (opcode == 0x1C) { + uint32 func = _insn & 0x3f; // 6-bits + // mul + if (func == 0x2) { + return uint32(int32(_rs) * int32(_rt)); + } + // clz, clo + else if (func == 0x20 || func == 0x21) { + if (func == 0x20) { + _rs = ~_rs; + } + uint32 i = 0; + while (_rs & 0x80000000 != 0) { + i++; + _rs <<= 1; + } + return i; + } + } + // lui + else if (opcode == 0x0F) { + return _rt << 16; + } + // lb + else if (opcode == 0x20) { + return signExtend((_mem >> (24 - (_rs & 3) * 8)) & 0xFF, 8); + } + // lh + else if (opcode == 0x21) { + return signExtend((_mem >> (16 - (_rs & 2) * 8)) & 0xFFFF, 16); + } + // lwl + else if (opcode == 0x22) { + uint32 val = _mem << ((_rs & 3) * 8); + uint32 mask = uint32(0xFFFFFFFF) << ((_rs & 3) * 8); + return (_rt & ~mask) | val; + } + // lw + else if (opcode == 0x23) { + return _mem; + } + // lbu + else if (opcode == 0x24) { + return (_mem >> (24 - (_rs & 3) * 8)) & 0xFF; + } + // lhu + else if (opcode == 0x25) { + return (_mem >> (16 - (_rs & 2) * 8)) & 0xFFFF; + } + // lwr + else if (opcode == 0x26) { + uint32 val = _mem >> (24 - (_rs & 3) * 8); + uint32 mask = uint32(0xFFFFFFFF) >> (24 - (_rs & 3) * 8); + return (_rt & ~mask) | val; + } + // sb + else if (opcode == 0x28) { + uint32 val = (_rt & 0xFF) << (24 - (_rs & 3) * 8); + uint32 mask = 0xFFFFFFFF ^ uint32(0xFF << (24 - (_rs & 3) * 8)); + return (_mem & mask) | val; + } + // sh + else if (opcode == 0x29) { + uint32 val = (_rt & 0xFFFF) << (16 - (_rs & 2) * 8); + uint32 mask = 0xFFFFFFFF ^ uint32(0xFFFF << (16 - (_rs & 2) * 8)); + return (_mem & mask) | val; + } + // swl + else if (opcode == 0x2a) { + uint32 val = _rt >> ((_rs & 3) * 8); + uint32 mask = uint32(0xFFFFFFFF) >> ((_rs & 3) * 8); + return (_mem & ~mask) | val; + } + // sw + else if (opcode == 0x2b) { + return _rt; + } + // swr + else if (opcode == 0x2e) { + uint32 val = _rt << (24 - (_rs & 3) * 8); + uint32 mask = uint32(0xFFFFFFFF) << (24 - (_rs & 3) * 8); + return (_mem & ~mask) | val; + } + // ll + else if (opcode == 0x30) { + return _mem; + } + // sc + else if (opcode == 0x38) { + return _rt; + } else { + revert("invalid instruction"); + } } + revert("invalid instruction"); + } + }   - // sll - if (func == 0x00) { - return rt << ((insn >> 6) & 0x1F); - } - // srl - else if (func == 0x02) { - return rt >> ((insn >> 6) & 0x1F); - } - // sra - else if (func == 0x03) { - uint32 shamt = (insn >> 6) & 0x1F; - return signExtend(rt >> shamt, 32 - shamt); - } - // sllv - else if (func == 0x04) { - return rt << (rs & 0x1F); - } - // srlv - else if (func == 0x6) { - return rt >> (rs & 0x1F); - } - // srav - else if (func == 0x07) { - return signExtend(rt >> rs, 32 - rs); - } - // functs in range [0x8, 0x1b] are handled specially by other functions - // Explicitly enumerate each funct in range to reduce code diff against Go Vm - // jr - else if (func == 0x08) { - return rs; - } - // jalr - else if (func == 0x09) { - return rs; - } - // movz - else if (func == 0x0a) { - return rs; - } - // movn - else if (func == 0x0b) { - return rs; - } - // syscall - else if (func == 0x0c) { - return rs; - } - // 0x0d - break not supported - // sync - else if (func == 0x0f) { - return rs; - } - // mfhi - else if (func == 0x10) { - return rs; - } - // mthi - else if (func == 0x11) { - return rs; - } - // mflo - else if (func == 0x12) { - return rs; + /// @notice Extends the value leftwards with its most significant bit (sign extension). + function signExtend(uint32 _dat, uint32 _idx) internal pure returns (uint32 out_) { + unchecked { + bool isSigned = (_dat >> (_idx - 1)) != 0; + uint256 signed = ((1 << (32 - _idx)) - 1) << _idx; + uint256 mask = (1 << _idx) - 1; + return uint32(_dat & mask | (isSigned ? signed : 0)); + } + } + + /// @notice Handles a branch instruction, updating the MIPS state PC where needed. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _opcode The opcode of the branch instruction. + /// @param _insn The instruction to be executed. + /// @param _rtReg The register to be used for the branch. + /// @param _rs The register to be compared with the branch register. + function handleBranch( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _opcode, + uint32 _insn, + uint32 _rtReg, + uint32 _rs + ) + internal + pure + { + unchecked { + bool shouldBranch = false; + + if (_cpu.nextPC != _cpu.pc + 4) { + revert("branch in delay slot"); } - // mtlo - else if (func == 0x13) { - return rs; + + // beq/bne: Branch on equal / not equal + if (_opcode == 4 || _opcode == 5) { + uint32 rt = _registers[_rtReg]; + shouldBranch = (_rs == rt && _opcode == 4) || (_rs != rt && _opcode == 5); } - // mult - else if (func == 0x18) { - return rs; + // blez: Branches if instruction is less than or equal to zero + else if (_opcode == 6) { + shouldBranch = int32(_rs) <= 0; } - // multu - else if (func == 0x19) { - return rs; - } - // div - else if (func == 0x1a) { - return rs; + // bgtz: Branches if instruction is greater than zero + else if (_opcode == 7) { + shouldBranch = int32(_rs) > 0; } - // divu - else if (func == 0x1b) { - return rs; - } - // The rest includes transformed R-type arith imm instructions - // add - else if (func == 0x20) { - return (rs + rt); - } - // addu - else if (func == 0x21) { - return (rs + rt); - } - // sub - else if (func == 0x22) { - return (rs - rt); - } - // subu - else if (func == 0x23) { - return (rs - rt); - } - // and - else if (func == 0x24) { - return (rs & rt); - } - // or - else if (func == 0x25) { - return (rs | rt); - } - // xor - else if (func == 0x26) { - return (rs ^ rt); - } - // nor - else if (func == 0x27) { - return ~(rs | rt); - } - // slti - else if (func == 0x2a) { - return int32(rs) < int32(rt) ? 1 : 0; - } - // sltiu - else if (func == 0x2b) { - return rs < rt ? 1 : 0; - } else { - revert("invalid instruction"); - } - } else { - // SPECIAL2 - if (opcode == 0x1C) { - uint32 func = insn & 0x3f; // 6-bits - // mul - if (func == 0x2) { - return uint32(int32(rs) * int32(rt)); + // bltz/bgez: Branch on less than zero / greater than or equal to zero + else if (_opcode == 1) { + // regimm + uint32 rtv = ((_insn >> 16) & 0x1F); + if (rtv == 0) { + shouldBranch = int32(_rs) < 0; } - // clz, clo - else if (func == 0x20 || func == 0x21) { - if (func == 0x20) { - rs = ~rs; - } - uint32 i = 0; - while (rs & 0x80000000 != 0) { - i++; - rs <<= 1; - } - return i; + if (rtv == 1) { + shouldBranch = int32(_rs) >= 0; } } - // lui - else if (opcode == 0x0F) { - return rt << 16; + + // Update the state's previous PC + uint32 prevPC = _cpu.pc; + + // Execute the delay slot first + _cpu.pc = _cpu.nextPC; + + // If we should branch, update the PC to the branch target + // Otherwise, proceed to the next instruction + if (shouldBranch) { + _cpu.nextPC = prevPC + 4 + (signExtend(_insn & 0xFFFF, 16) << 2); + } else { + _cpu.nextPC = _cpu.nextPC + 4; } - // lb - else if (opcode == 0x20) { - return signExtend((mem >> (24 - (rs & 3) * 8)) & 0xFF, 8); + } + } + + /// @notice Handles HI and LO register instructions. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _func The function code of the instruction. + /// @param _rs The value of the RS register. + /// @param _rt The value of the RT register. + /// @param _storeReg The register to store the result in. + function handleHiLo( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _func, + uint32 _rs, + uint32 _rt, + uint32 _storeReg + ) + internal + pure + { + unchecked { + uint32 val = 0; + + // mfhi: Move the contents of the HI register into the destination + if (_func == 0x10) { + val = _cpu.hi; } - // lh - else if (opcode == 0x21) { - return signExtend((mem >> (16 - (rs & 2) * 8)) & 0xFFFF, 16); + // mthi: Move the contents of the source into the HI register + else if (_func == 0x11) { + _cpu.hi = _rs; } - // lwl - else if (opcode == 0x22) { - uint32 val = mem << ((rs & 3) * 8); - uint32 mask = uint32(0xFFFFFFFF) << ((rs & 3) * 8); - return (rt & ~mask) | val; + // mflo: Move the contents of the LO register into the destination + else if (_func == 0x12) { + val = _cpu.lo; } - // lw - else if (opcode == 0x23) { - return mem; + // mtlo: Move the contents of the source into the LO register + else if (_func == 0x13) { + _cpu.lo = _rs; } - // lbu - else if (opcode == 0x24) { - return (mem >> (24 - (rs & 3) * 8)) & 0xFF; - } - // lhu - else if (opcode == 0x25) { - return (mem >> (16 - (rs & 2) * 8)) & 0xFFFF; - } - // lwr - else if (opcode == 0x26) { - uint32 val = mem >> (24 - (rs & 3) * 8); - uint32 mask = uint32(0xFFFFFFFF) >> (24 - (rs & 3) * 8); - return (rt & ~mask) | val; - } - // sb - else if (opcode == 0x28) { - uint32 val = (rt & 0xFF) << (24 - (rs & 3) * 8); - uint32 mask = 0xFFFFFFFF ^ uint32(0xFF << (24 - (rs & 3) * 8)); - return (mem & mask) | val; + // mult: Multiplies `rs` by `rt` and stores the result in HI and LO registers + else if (_func == 0x18) { + uint64 acc = uint64(int64(int32(_rs)) * int64(int32(_rt))); + _cpu.hi = uint32(acc >> 32); + _cpu.lo = uint32(acc); } - // sh - else if (opcode == 0x29) { - uint32 val = (rt & 0xFFFF) << (16 - (rs & 2) * 8); - uint32 mask = 0xFFFFFFFF ^ uint32(0xFFFF << (16 - (rs & 2) * 8)); - return (mem & mask) | val; + // multu: Unsigned multiplies `rs` by `rt` and stores the result in HI and LO registers + else if (_func == 0x19) { + uint64 acc = uint64(uint64(_rs) * uint64(_rt)); + _cpu.hi = uint32(acc >> 32); + _cpu.lo = uint32(acc); } - // swl - else if (opcode == 0x2a) { - uint32 val = rt >> ((rs & 3) * 8); - uint32 mask = uint32(0xFFFFFFFF) >> ((rs & 3) * 8); - return (mem & ~mask) | val; + // div: Divides `rs` by `rt`. + // Stores the quotient in LO + // And the remainder in HI + else if (_func == 0x1a) { + if (int32(_rt) == 0) { + revert("MIPS: division by zero"); + } + _cpu.hi = uint32(int32(_rs) % int32(_rt)); + _cpu.lo = uint32(int32(_rs) / int32(_rt)); } - // sw - else if (opcode == 0x2b) { - return rt; + // divu: Unsigned divides `rs` by `rt`. + // Stores the quotient in LO + // And the remainder in HI + else if (_func == 0x1b) { + if (_rt == 0) { + revert("MIPS: division by zero"); + } + _cpu.hi = _rs % _rt; + _cpu.lo = _rs / _rt; } - // swr - else if (opcode == 0x2e) { - uint32 val = rt << (24 - (rs & 3) * 8); - uint32 mask = uint32(0xFFFFFFFF) << (24 - (rs & 3) * 8); - return (mem & ~mask) | val; + + // Store the result in the destination register, if applicable + if (_storeReg != 0) { + _registers[_storeReg] = val; } - // ll - else if (opcode == 0x30) { - return mem; + + // Update the PC + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } + } + + /// @notice Handles a jump instruction, updating the MIPS state PC where needed. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _linkReg The register to store the link to the instruction after the delay slot instruction. + /// @param _dest The destination to jump to. + function handleJump( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _linkReg, + uint32 _dest + ) + internal + pure + { + unchecked { + if (_cpu.nextPC != _cpu.pc + 4) { + revert("jump in delay slot"); } - // sc - else if (opcode == 0x38) { - return rt; - } else { - revert("invalid instruction"); + + // Update the next PC to the jump destination. + uint32 prevPC = _cpu.pc; + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _dest; + + // Update the link-register to the instruction after the delay slot instruction. + if (_linkReg != 0) { + _registers[_linkReg] = prevPC + 8; } } - revert("invalid instruction"); } -} + + /// @notice Handles a storing a value into a register. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _storeReg The register to store the value into. + /// @param _val The value to store. + /// @param _conditional Whether or not the store is conditional. + function handleRd( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _storeReg, + uint32 _val, + bool _conditional + ) + internal + pure + { + unchecked { + // The destination register must be valid. + require(_storeReg < 32, "valid register"); + + // Never write to reg 0, and it can be conditional (movz, movn). + if (_storeReg != 0 && _conditional) { + _registers[_storeReg] = _val; + }   -/// @notice Extends the value leftwards with its most significant bit (sign extension). -function signExtend(uint32 _dat, uint32 _idx) pure returns (uint32 out_) { - unchecked { - bool isSigned = (_dat >> (_idx - 1)) != 0; - uint256 signed = ((1 << (32 - _idx)) - 1) << _idx; - uint256 mask = (1 << _idx) - 1; - return uint32(_dat & mask | (isSigned ? signed : 0)); + // Update the PC. + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } } }
diff --git OP/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol CELO/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol new file mode 100644 index 0000000000000000000000000000000000000000..a8d1b87dcbe05b9da0a48024fd809db0235ef3a4 --- /dev/null +++ CELO/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +library MIPSMemory { + /// @notice Reads a 32-bit value from memory. + /// @param _memRoot The current memory root + /// @param _addr The address to read from. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @return out_ The hashed MIPS state. + function readMem(bytes32 _memRoot, uint32 _addr, uint256 _proofOffset) internal pure returns (uint32 out_) { + unchecked { + validateMemoryProofAvailability(_proofOffset); + assembly { + // Validate the address alignement. + if and(_addr, 3) { revert(0, 0) } + + // Load the leaf value. + let leaf := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + + // Convenience function to hash two nodes together in scratch space. + function hashPair(a, b) -> h { + mstore(0, a) + mstore(32, b) + h := keccak256(0, 64) + } + + // Start with the leaf node. + // Work back up by combining with siblings, to reconstruct the root. + let path := shr(5, _addr) + let node := leaf + for { let i := 0 } lt(i, 27) { i := add(i, 1) } { + let sibling := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + switch and(shr(i, path), 1) + case 0 { node := hashPair(node, sibling) } + case 1 { node := hashPair(sibling, node) } + } + + // Verify the root matches. + if iszero(eq(node, _memRoot)) { + mstore(0, 0x0badf00d) + revert(0, 32) + } + + // Bits to shift = (32 - 4 - (addr % 32)) * 8 + let shamt := shl(3, sub(sub(32, 4), and(_addr, 31))) + out_ := and(shr(shamt, leaf), 0xFFffFFff) + } + } + } + + /// @notice Writes a 32-bit value to memory. + /// This function first overwrites the part of the leaf. + /// Then it recomputes the memory merkle root. + /// @param _addr The address to write to. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @param _val The value to write. + /// @return newMemRoot_ The new memory root after modification + function writeMem(uint32 _addr, uint256 _proofOffset, uint32 _val) internal pure returns (bytes32 newMemRoot_) { + unchecked { + validateMemoryProofAvailability(_proofOffset); + assembly { + // Validate the address alignement. + if and(_addr, 3) { revert(0, 0) } + + // Load the leaf value. + let leaf := calldataload(_proofOffset) + let shamt := shl(3, sub(sub(32, 4), and(_addr, 31))) + + // Mask out 4 bytes, and OR in the value + leaf := or(and(leaf, not(shl(shamt, 0xFFffFFff))), shl(shamt, _val)) + _proofOffset := add(_proofOffset, 32) + + // Convenience function to hash two nodes together in scratch space. + function hashPair(a, b) -> h { + mstore(0, a) + mstore(32, b) + h := keccak256(0, 64) + } + + // Start with the leaf node. + // Work back up by combining with siblings, to reconstruct the root. + let path := shr(5, _addr) + let node := leaf + for { let i := 0 } lt(i, 27) { i := add(i, 1) } { + let sibling := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + switch and(shr(i, path), 1) + case 0 { node := hashPair(node, sibling) } + case 1 { node := hashPair(sibling, node) } + } + + newMemRoot_ := node + } + } + return newMemRoot_; + } + + /// @notice Computes the offset of a memory proof in the calldata. + /// @param _proofDataOffset The offset of the set of all memory proof data within calldata (proof.offset) + /// Equal to the offset of the first memory proof (at _proofIndex 0). + /// @param _proofIndex The index of the proof in the calldata. + /// @return offset_ The offset of the memory proof at the given _proofIndex in the calldata. + function memoryProofOffset(uint256 _proofDataOffset, uint8 _proofIndex) internal pure returns (uint256 offset_) { + unchecked { + // A proof of 32 bit memory, with 32-byte leaf values, is (32-5)=27 bytes32 entries. + // And the leaf value itself needs to be encoded as well: (27 + 1) = 28 bytes32 entries. + offset_ = _proofDataOffset + (uint256(_proofIndex) * (28 * 32)); + return offset_; + } + } + + /// @notice Validates that enough calldata is available to hold a full memory proof at the given offset + /// @param _proofStartOffset The index of the first byte of the target memory proof in calldata + function validateMemoryProofAvailability(uint256 _proofStartOffset) internal pure { + uint256 s = 0; + assembly { + s := calldatasize() + } + // A memory proof consists of 28 bytes32 values - verify we have enough calldata + require(s >= (_proofStartOffset + 28 * 32), "check that there is enough calldata"); + } +}
diff --git OP/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol CELO/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol new file mode 100644 index 0000000000000000000000000000000000000000..d4431765c5b0c7805428840c86be40b72c6c54ea --- /dev/null +++ CELO/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +library MIPSState { + struct CpuScalars { + uint32 pc; + uint32 nextPC; + uint32 lo; + uint32 hi; + } +}
diff --git OP/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol CELO/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol new file mode 100644 index 0000000000000000000000000000000000000000..bc629a24623eb66820045266b8d99a1a7533be2e --- /dev/null +++ CELO/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; +import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; +import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; + +library MIPSSyscalls { + uint32 internal constant SYS_MMAP = 4090; + uint32 internal constant SYS_BRK = 4045; + uint32 internal constant SYS_CLONE = 4120; + uint32 internal constant SYS_EXIT_GROUP = 4246; + uint32 internal constant SYS_READ = 4003; + uint32 internal constant SYS_WRITE = 4004; + uint32 internal constant SYS_FCNTL = 4055; + + uint32 internal constant FD_STDIN = 0; + uint32 internal constant FD_STDOUT = 1; + uint32 internal constant FD_STDERR = 2; + uint32 internal constant FD_HINT_READ = 3; + uint32 internal constant FD_HINT_WRITE = 4; + uint32 internal constant FD_PREIMAGE_READ = 5; + uint32 internal constant FD_PREIMAGE_WRITE = 6; + + uint32 internal constant EBADF = 0x9; + uint32 internal constant EINVAL = 0x16; + + /// @notice Extract syscall num and arguments from registers. + /// @param _registers The cpu registers. + /// @return sysCallNum_ The syscall number. + /// @return a0_ The first argument available to the syscall operation. + /// @return a1_ The second argument available to the syscall operation. + /// @return a2_ The third argument available to the syscall operation. + function getSyscallArgs(uint32[32] memory _registers) + internal + pure + returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_) + { + sysCallNum_ = _registers[2]; + + a0_ = _registers[4]; + a1_ = _registers[5]; + a2_ = _registers[6]; + + return (sysCallNum_, a0_, a1_, a2_); + } + + /// @notice Like a Linux mmap syscall. Allocates a page from the heap. + /// @param _a0 The address for the new mapping + /// @param _a1 The size of the new mapping + /// @param _heap The current value of the heap pointer + /// @return v0_ The address of the new mapping + /// @return v1_ Unused error code (0) + /// @return newHeap_ The new value for the heap, may be unchanged + function handleSysMmap( + uint32 _a0, + uint32 _a1, + uint32 _heap + ) + internal + pure + returns (uint32 v0_, uint32 v1_, uint32 newHeap_) + { + v1_ = uint32(0); + newHeap_ = _heap; + + uint32 sz = _a1; + if (sz & 4095 != 0) { + // adjust size to align with page size + sz += 4096 - (sz & 4095); + } + if (_a0 == 0) { + v0_ = _heap; + newHeap_ += sz; + } else { + v0_ = _a0; + } + + return (v0_, v1_, newHeap_); + } + + /// @notice Like a Linux read syscall. Splits unaligned reads into aligned reads. + /// @param _a0 The file descriptor. + /// @param _a1 The memory location where data should be read to. + /// @param _a2 The number of bytes to read from the file + /// @param _preimageKey The key of the preimage to read. + /// @param _preimageOffset The offset of the preimage to read. + /// @param _localContext The local context for the preimage key. + /// @param _oracle The address of the preimage oracle. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @param _memRoot The current memory root. + /// @return v0_ The number of bytes read, -1 on error. + /// @return v1_ The error code, 0 if there is no error. + /// @return newPreimageOffset_ The new value for the preimage offset. + /// @return newMemRoot_ The new memory root. + function handleSysRead( + uint32 _a0, + uint32 _a1, + uint32 _a2, + bytes32 _preimageKey, + uint32 _preimageOffset, + bytes32 _localContext, + IPreimageOracle _oracle, + uint256 _proofOffset, + bytes32 _memRoot + ) + internal + view + returns (uint32 v0_, uint32 v1_, uint32 newPreimageOffset_, bytes32 newMemRoot_) + { + v0_ = uint32(0); + v1_ = uint32(0); + newMemRoot_ = _memRoot; + newPreimageOffset_ = _preimageOffset; + + // args: _a0 = fd, _a1 = addr, _a2 = count + // returns: v0_ = read, v1_ = err code + if (_a0 == FD_STDIN) { + // Leave v0_ and v1_ zero: read nothing, no error + } + // pre-image oracle read + else if (_a0 == FD_PREIMAGE_READ) { + // verify proof is correct, and get the existing memory. + // mask the addr to align it to 4 bytes + uint32 mem = MIPSMemory.readMem(_memRoot, _a1 & 0xFFffFFfc, _proofOffset); + // If the preimage key is a local key, localize it in the context of the caller. + if (uint8(_preimageKey[0]) == 1) { + _preimageKey = PreimageKeyLib.localize(_preimageKey, _localContext); + } + (bytes32 dat, uint256 datLen) = _oracle.readPreimage(_preimageKey, _preimageOffset); + + // Transform data for writing to memory + // We use assembly for more precise ops, and no var count limit + assembly { + let alignment := and(_a1, 3) // the read might not start at an aligned address + let space := sub(4, alignment) // remaining space in memory word + if lt(space, datLen) { datLen := space } // if less space than data, shorten data + if lt(_a2, datLen) { datLen := _a2 } // if requested to read less, read less + dat := shr(sub(256, mul(datLen, 8)), dat) // right-align data + dat := shl(mul(sub(sub(4, datLen), alignment), 8), dat) // position data to insert into memory + // word + let mask := sub(shl(mul(sub(4, alignment), 8), 1), 1) // mask all bytes after start + let suffixMask := sub(shl(mul(sub(sub(4, alignment), datLen), 8), 1), 1) // mask of all bytes + // starting from end, maybe none + mask := and(mask, not(suffixMask)) // reduce mask to just cover the data we insert + mem := or(and(mem, not(mask)), dat) // clear masked part of original memory, and insert data + } + + // Write memory back + newMemRoot_ = MIPSMemory.writeMem(_a1 & 0xFFffFFfc, _proofOffset, mem); + newPreimageOffset_ += uint32(datLen); + v0_ = uint32(datLen); + } + // hint response + else if (_a0 == FD_HINT_READ) { + // Don't read into memory, just say we read it all + // The result is ignored anyway + v0_ = _a2; + } else { + v0_ = 0xFFffFFff; + v1_ = EBADF; + } + + return (v0_, v1_, newPreimageOffset_, newMemRoot_); + } + + /// @notice Like a Linux write syscall. Splits unaligned writes into aligned writes. + /// @param _a0 The file descriptor. + /// @param _a1 The memory address to read from. + /// @param _a2 The number of bytes to read. + /// @param _preimageKey The current preimaageKey. + /// @param _preimageOffset The current preimageOffset. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @param _memRoot The current memory root. + /// @return v0_ The number of bytes written, or -1 on error. + /// @return v1_ The error code, or 0 if empty. + /// @return newPreimageKey_ The new preimageKey. + /// @return newPreimageOffset_ The new preimageOffset. + function handleSysWrite( + uint32 _a0, + uint32 _a1, + uint32 _a2, + bytes32 _preimageKey, + uint32 _preimageOffset, + uint256 _proofOffset, + bytes32 _memRoot + ) + internal + pure + returns (uint32 v0_, uint32 v1_, bytes32 newPreimageKey_, uint32 newPreimageOffset_) + { + // args: _a0 = fd, _a1 = addr, _a2 = count + // returns: v0_ = written, v1_ = err code + v0_ = uint32(0); + v1_ = uint32(0); + newPreimageKey_ = _preimageKey; + newPreimageOffset_ = _preimageOffset; + + if (_a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_HINT_WRITE) { + v0_ = _a2; // tell program we have written everything + } + // pre-image oracle + else if (_a0 == FD_PREIMAGE_WRITE) { + // mask the addr to align it to 4 bytes + uint32 mem = MIPSMemory.readMem(_memRoot, _a1 & 0xFFffFFfc, _proofOffset); + bytes32 key = _preimageKey; + + // Construct pre-image key from memory + // We use assembly for more precise ops, and no var count limit + assembly { + let alignment := and(_a1, 3) // the read might not start at an aligned address + let space := sub(4, alignment) // remaining space in memory word + if lt(space, _a2) { _a2 := space } // if less space than data, shorten data + key := shl(mul(_a2, 8), key) // shift key, make space for new info + let mask := sub(shl(mul(_a2, 8), 1), 1) // mask for extracting value from memory + mem := and(shr(mul(sub(space, _a2), 8), mem), mask) // align value to right, mask it + key := or(key, mem) // insert into key + } + + // Write pre-image key to oracle + newPreimageKey_ = key; + newPreimageOffset_ = 0; // reset offset, to read new pre-image data from the start + v0_ = _a2; + } else { + v0_ = 0xFFffFFff; + v1_ = EBADF; + } + + return (v0_, v1_, newPreimageKey_, newPreimageOffset_); + } + + /// @notice Like Linux fcntl (file control) syscall, but only supports minimal file-descriptor control commands, to + /// retrieve the file-descriptor R/W flags. + /// @param _a0 The file descriptor. + /// @param _a1 The control command. + /// @param v0_ The file status flag (only supported command is F_GETFL), or -1 on error. + /// @param v1_ An error number, or 0 if there is no error. + function handleSysFcntl(uint32 _a0, uint32 _a1) internal pure returns (uint32 v0_, uint32 v1_) { + v0_ = uint32(0); + v1_ = uint32(0); + + // args: _a0 = fd, _a1 = cmd + if (_a1 == 3) { + // F_GETFL: get file descriptor flags + if (_a0 == FD_STDIN || _a0 == FD_PREIMAGE_READ || _a0 == FD_HINT_READ) { + v0_ = 0; // O_RDONLY + } else if (_a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE) { + v0_ = 1; // O_WRONLY + } else { + v0_ = 0xFFffFFff; + v1_ = EBADF; + } + } else { + v0_ = 0xFFffFFff; + v1_ = EINVAL; // cmd not recognized by this kernel + } + + return (v0_, v1_); + } + + function handleSyscallUpdates( + st.CpuScalars memory _cpu, + uint32[32] memory _registers, + uint32 _v0, + uint32 _v1 + ) + internal + pure + { + // Write the results back to the state registers + _registers[2] = _v0; + _registers[7] = _v1; + + // Update the PC and nextPC + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/common/FixidityLib.sol CELO/packages/contracts-bedrock/src/celo/common/FixidityLib.sol new file mode 100644 index 0000000000000000000000000000000000000000..613da18562198968356b17cffa9890cb364b2063 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/FixidityLib.sol @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title FixidityLib + * @author Gadi Guy, Alberto Cuesta Canada + * @notice This library provides fixed point arithmetic with protection against + * overflow. + * All operations are done with uint256 and the operands must have been created + * with any of the newFrom* functions, which shift the comma digits() to the + * right and check for limits, or with wrap() which expects a number already + * in the internal representation of a fraction. + * When using this library be sure to use maxNewFixed() as the upper limit for + * creation of fixed point numbers. + * @dev All contained functions are pure and thus marked internal to be inlined + * on consuming contracts at compile time for gas efficiency. + */ +library FixidityLib { + struct Fraction { + uint256 value; + } + + /** + * @notice Number of positions that the comma is shifted to the right. + */ + function digits() internal pure returns (uint8) { + return 24; + } + + uint256 private constant FIXED1_UINT = 1000000000000000000000000; + + /** + * @notice This is 1 in the fixed point units used in this library. + * @dev Test fixed1() equals 10^digits() + * Hardcoded to 24 digits. + */ + function fixed1() internal pure returns (Fraction memory) { + return Fraction(FIXED1_UINT); + } + + /** + * @notice Wrap a uint256 that represents a 24-decimal fraction in a Fraction + * struct. + * @param x Number that already represents a 24-decimal fraction. + * @return A Fraction struct with contents x. + */ + function wrap(uint256 x) internal pure returns (Fraction memory) { + return Fraction(x); + } + + /** + * @notice Unwraps the uint256 inside of a Fraction struct. + */ + function unwrap(Fraction memory x) internal pure returns (uint256) { + return x.value; + } + + /** + * @notice The amount of decimals lost on each multiplication operand. + * @dev Test mulPrecision() equals sqrt(fixed1) + */ + function mulPrecision() internal pure returns (uint256) { + return 1000000000000; + } + + /** + * @notice Maximum value that can be converted to fixed point. Optimize for deployment. + * @dev + * Test maxNewFixed() equals maxUint256() / fixed1() + */ + function maxNewFixed() internal pure returns (uint256) { + return 115792089237316195423570985008687907853269984665640564; + } + + /** + * @notice Converts a uint256 to fixed point Fraction + * @dev Test newFixed(0) returns 0 + * Test newFixed(1) returns fixed1() + * Test newFixed(maxNewFixed()) returns maxNewFixed() * fixed1() + * Test newFixed(maxNewFixed()+1) fails + */ + function newFixed(uint256 x) internal pure returns (Fraction memory) { + require(x <= maxNewFixed(), "can't create fixidity number larger than maxNewFixed()"); + return Fraction(x * FIXED1_UINT); + } + + /** + * @notice Converts a uint256 in the fixed point representation of this + * library to a non decimal. All decimal digits will be truncated. + */ + function fromFixed(Fraction memory x) internal pure returns (uint256) { + return x.value / FIXED1_UINT; + } + + /** + * @notice Converts two uint256 representing a fraction to fixed point units, + * equivalent to multiplying dividend and divisor by 10^digits(). + * @param numerator numerator must be <= maxNewFixed() + * @param denominator denominator must be <= maxNewFixed() and denominator can't be 0 + * @dev + * Test newFixedFraction(1,0) fails + * Test newFixedFraction(0,1) returns 0 + * Test newFixedFraction(1,1) returns fixed1() + * Test newFixedFraction(1,fixed1()) returns 1 + */ + function newFixedFraction(uint256 numerator, uint256 denominator) internal pure returns (Fraction memory) { + Fraction memory convertedNumerator = newFixed(numerator); + Fraction memory convertedDenominator = newFixed(denominator); + return divide(convertedNumerator, convertedDenominator); + } + + /** + * @notice Returns the integer part of a fixed point number. + * @dev + * Test integer(0) returns 0 + * Test integer(fixed1()) returns fixed1() + * Test integer(newFixed(maxNewFixed())) returns maxNewFixed()*fixed1() + */ + function integer(Fraction memory x) internal pure returns (Fraction memory) { + return Fraction((x.value / FIXED1_UINT) * FIXED1_UINT); // Can't overflow + } + + /** + * @notice Returns the fractional part of a fixed point number. + * In the case of a negative number the fractional is also negative. + * @dev + * Test fractional(0) returns 0 + * Test fractional(fixed1()) returns 0 + * Test fractional(fixed1()-1) returns 10^24-1 + */ + function fractional(Fraction memory x) internal pure returns (Fraction memory) { + return Fraction(x.value - (x.value / FIXED1_UINT) * FIXED1_UINT); // Can't overflow + } + + /** + * @notice x+y. + * @dev The maximum value that can be safely used as an addition operator is defined as + * maxFixedAdd = maxUint256()-1 / 2, or + * 57896044618658097711785492504343953926634992332820282019728792003956564819967. + * Test add(maxFixedAdd,maxFixedAdd) equals maxFixedAdd + maxFixedAdd + * Test add(maxFixedAdd+1,maxFixedAdd+1) throws + */ + function add(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + uint256 z = x.value + y.value; + require(z >= x.value, "add overflow detected"); + return Fraction(z); + } + + /** + * @notice x-y. + * @dev + * Test subtract(6, 10) fails + */ + function subtract(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + require(x.value >= y.value, "substraction underflow detected"); + return Fraction(x.value - y.value); + } + + /** + * @notice x*y. If any of the operators is higher than the max multiplier value it + * might overflow. + * @dev The maximum value that can be safely used as a multiplication operator + * (maxFixedMul) is calculated as sqrt(maxUint256()*fixed1()), + * or 340282366920938463463374607431768211455999999999999 + * Test multiply(0,0) returns 0 + * Test multiply(maxFixedMul,0) returns 0 + * Test multiply(0,maxFixedMul) returns 0 + * Test multiply(fixed1()/mulPrecision(),fixed1()*mulPrecision()) returns fixed1() + * Test multiply(maxFixedMul,maxFixedMul) is around maxUint256() + * Test multiply(maxFixedMul+1,maxFixedMul+1) fails + */ + function multiply(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + if (x.value == 0 || y.value == 0) return Fraction(0); + if (y.value == FIXED1_UINT) return x; + if (x.value == FIXED1_UINT) return y; + + // Separate into integer and fractional parts + // x = x1 + x2, y = y1 + y2 + uint256 x1 = integer(x).value / FIXED1_UINT; + uint256 x2 = fractional(x).value; + uint256 y1 = integer(y).value / FIXED1_UINT; + uint256 y2 = fractional(y).value; + + // (x1 + x2) * (y1 + y2) = (x1 * y1) + (x1 * y2) + (x2 * y1) + (x2 * y2) + uint256 x1y1 = x1 * y1; + if (x1 != 0) require(x1y1 / x1 == y1, "overflow x1y1 detected"); + + // x1y1 needs to be multiplied back by fixed1 + // solium-disable-next-line mixedcase + uint256 fixed_x1y1 = x1y1 * FIXED1_UINT; + if (x1y1 != 0) require(fixed_x1y1 / x1y1 == FIXED1_UINT, "overflow x1y1 * fixed1 detected"); + x1y1 = fixed_x1y1; + + uint256 x2y1 = x2 * y1; + if (x2 != 0) require(x2y1 / x2 == y1, "overflow x2y1 detected"); + + uint256 x1y2 = x1 * y2; + if (x1 != 0) require(x1y2 / x1 == y2, "overflow x1y2 detected"); + + x2 = x2 / mulPrecision(); + y2 = y2 / mulPrecision(); + uint256 x2y2 = x2 * y2; + if (x2 != 0) require(x2y2 / x2 == y2, "overflow x2y2 detected"); + + // result = fixed1() * x1 * y1 + x1 * y2 + x2 * y1 + x2 * y2 / fixed1(); + Fraction memory result = Fraction(x1y1); + result = add(result, Fraction(x2y1)); // Add checks for overflow + result = add(result, Fraction(x1y2)); // Add checks for overflow + result = add(result, Fraction(x2y2)); // Add checks for overflow + return result; + } + + /** + * @notice 1/x + * @dev + * Test reciprocal(0) fails + * Test reciprocal(fixed1()) returns fixed1() + * Test reciprocal(fixed1()*fixed1()) returns 1 // Testing how the fractional is truncated + * Test reciprocal(1+fixed1()*fixed1()) returns 0 // Testing how the fractional is truncated + * Test reciprocal(newFixedFraction(1, 1e24)) returns newFixed(1e24) + */ + function reciprocal(Fraction memory x) internal pure returns (Fraction memory) { + require(x.value != 0, "can't call reciprocal(0)"); + return Fraction((FIXED1_UINT * FIXED1_UINT) / x.value); // Can't overflow + } + + /** + * @notice x/y. If the dividend is higher than the max dividend value, it + * might overflow. You can use multiply(x,reciprocal(y)) instead. + * @dev The maximum value that can be safely used as a dividend (maxNewFixed) is defined as + * divide(maxNewFixed,newFixedFraction(1,fixed1())) is around maxUint256(). + * This yields the value 115792089237316195423570985008687907853269984665640564. + * Test maxNewFixed equals maxUint256()/fixed1() + * Test divide(maxNewFixed,1) equals maxNewFixed*(fixed1) + * Test divide(maxNewFixed+1,multiply(mulPrecision(),mulPrecision())) throws + * Test divide(fixed1(),0) fails + * Test divide(maxNewFixed,1) = maxNewFixed*(10^digits()) + * Test divide(maxNewFixed+1,1) throws + */ + function divide(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + require(y.value != 0, "can't divide by 0"); + uint256 X = x.value * FIXED1_UINT; + require(X / FIXED1_UINT == x.value, "overflow at divide"); + return Fraction(X / y.value); + } + + /** + * @notice x > y + */ + function gt(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value > y.value; + } + + /** + * @notice x >= y + */ + function gte(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value >= y.value; + } + + /** + * @notice x < y + */ + function lt(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value < y.value; + } + + /** + * @notice x <= y + */ + function lte(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value <= y.value; + } + + /** + * @notice x == y + */ + function equals(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value == y.value; + } + + /** + * @notice x <= 1 + */ + function isProperFraction(Fraction memory x) internal pure returns (bool) { + return lte(x, fixed1()); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/common/Freezable.sol CELO/packages/contracts-bedrock/src/celo/common/Freezable.sol new file mode 100644 index 0000000000000000000000000000000000000000..7541ea6fa5717fbe7905c58637c276771a63f9cb --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/Freezable.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../UsingRegistry.sol"; + +contract Freezable is UsingRegistry { + // onlyWhenNotFrozen functions can only be called when `frozen` is false, otherwise they will + // revert. + modifier onlyWhenNotFrozen() { + require(!getFreezer().isFrozen(address(this)), "can't call when contract is frozen"); + _; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/common/Initializable.sol CELO/packages/contracts-bedrock/src/celo/common/Initializable.sol new file mode 100644 index 0000000000000000000000000000000000000000..92baac5494d3b0324d3d506d0607f38199028b30 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/Initializable.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +contract Initializable { + bool public initialized; + + constructor(bool testingDeployment) { + if (!testingDeployment) { + initialized = true; + } + } + + modifier initializer() { + require(!initialized, "contract already initialized"); + initialized = true; + _; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol CELO/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol new file mode 100644 index 0000000000000000000000000000000000000000..5bf2033f31726110e6504078561108cfee40a42d --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the non- ERC20 shared interface for all Celo Tokens, and + * in the absence of interface inheritance is intended as a companion to IERC20.sol. + */ +interface ICeloToken { + function transferWithComment(address, uint256, string calldata) external returns (bool); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function burn(uint256 value) external returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol CELO/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol new file mode 100644 index 0000000000000000000000000000000000000000..37b1538c2a121b4dd73b5762db9ba4a97364581c --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface ICeloVersionedContract { + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256); +}
diff --git OP/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol CELO/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol new file mode 100644 index 0000000000000000000000000000000000000000..b707a446a685ac27778cd547f44932a4d92c127b --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +import "../FixidityLib.sol"; + +interface IFeeHandler { + // sets the portion of the fee that should be burned. + function setBurnFraction(uint256 fraction) external; + + function addToken(address tokenAddress, address handlerAddress) external; + function removeToken(address tokenAddress) external; + + function setHandler(address tokenAddress, address handlerAddress) external; + + // marks token to be handled in "handleAll()) + function activateToken(address tokenAddress) external; + function deactivateToken(address tokenAddress) external; + + function sell(address tokenAddress) external; + + // calls exchange(tokenAddress), and distribute(tokenAddress) + function handle(address tokenAddress) external; + + // main entrypoint for a burn, iterates over token and calles handle + function handleAll() external; + + // Sends the balance of token at tokenAddress to feesBeneficiary, + // according to the entry tokensToDistribute[tokenAddress] + function distribute(address tokenAddress) external; + + // burns the balance of Celo in the contract minus the entry of tokensToDistribute[CeloAddress] + function burnCelo() external; + + // calls distribute for all the nonCeloTokens + function distributeAll() external; + + // in case some funds need to be returned or moved to another contract + function transfer(address token, address recipient, uint256 value) external returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol CELO/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol new file mode 100644 index 0000000000000000000000000000000000000000..c3a9df0ee324a675f00c5b49f9f615a42427e815 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +import "../FixidityLib.sol"; + +interface IFeeHandlerSeller { + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 minAmount + ) + external + returns (uint256); + // in case some funds need to be returned or moved to another contract + function transfer(address token, uint256 amount, address to) external returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol CELO/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol new file mode 100644 index 0000000000000000000000000000000000000000..38ae7359e0e069a46e616d774be857852d3a98a0 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "@openzeppelin/contracts/utils/math/Math.sol"; + +import "./SortedLinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by address. + */ +library AddressSortedLinkedList { + using SortedLinkedList for SortedLinkedList.List; + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert( + SortedLinkedList.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.insert(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(SortedLinkedList.List storage list, address key) public { + list.remove(toBytes(key)); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update( + SortedLinkedList.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.update(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(SortedLinkedList.List storage list, address key) public view returns (bool) { + return list.contains(toBytes(key)); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(SortedLinkedList.List storage list, address key) public view returns (uint256) { + return list.getValue(toBytes(key)); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + */ + function getElements(SortedLinkedList.List storage list) public view returns (address[] memory, uint256[] memory) { + bytes32[] memory byteKeys = list.getKeys(); + address[] memory keys = new address[](byteKeys.length); + uint256[] memory values = new uint256[](byteKeys.length); + for (uint256 i = 0; i < byteKeys.length; i = i + 1) { + keys[i] = toAddress(byteKeys[i]); + values[i] = list.values[byteKeys[i]]; + } + return (keys, values); + } + + /** + * @notice Returns the minimum of `max` and the number of elements in the list > threshold. + * @param list A storage pointer to the underlying list. + * @param threshold The number that the element must exceed to be included. + * @param max The maximum number returned by this function. + * @return The minimum of `max` and the number of elements in the list > threshold. + */ + function numElementsGreaterThan( + SortedLinkedList.List storage list, + uint256 threshold, + uint256 max + ) + public + view + returns (uint256) + { + uint256 revisedMax = Math.min(max, list.list.numElements); + bytes32 key = list.list.head; + for (uint256 i = 0; i < revisedMax; i = i + 1) { + if (list.getValue(key) < threshold) { + return i; + } + key = list.list.elements[key].previousKey; + } + return revisedMax; + } + + /** + * @notice Returns the N greatest elements of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the greatest elements. + */ + function headN(SortedLinkedList.List storage list, uint256 n) public view returns (address[] memory) { + bytes32[] memory byteKeys = list.headN(n); + address[] memory keys = new address[](n); + for (uint256 i = 0; i < n; i = i + 1) { + keys[i] = toAddress(byteKeys[i]); + } + return keys; + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(SortedLinkedList.List storage list) public view returns (address[] memory) { + return headN(list, list.list.numElements); + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(SortedLinkedList.List storage list) public view returns (uint256) { + return list.list.numElements; + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(SortedLinkedList.List storage list) public view returns (address) { + return toAddress(list.list.head); + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(SortedLinkedList.List storage list) public view returns (address) { + return toAddress(list.list.tail); + } + + /** + * @notice Gets lesser and greater for address that has increased it's value. + * @param list A storage pointer to the underlying list. + * @param group The original address. + * @param newValue New value that has to be bigger or equal than the previous one. + * @param loopLimit The max limit of loops that will be executed. + */ + function getLesserAndGreaterOfAddressThatIncreasedValue( + SortedLinkedList.List storage list, + address group, + uint256 newValue, + uint256 loopLimit + ) + public + view + returns (address previous, address next) + { + (, previous, next) = get(list, group); + + while (next != address(0) && loopLimit != 0 && newValue > getValue(list, next)) { + previous = next; + (,, next) = get(list, previous); + loopLimit--; + } + + if (loopLimit == 0) { + return (address(0), address(0)); + } + } + + /** + * @notice Gets lesser and greater for address that has decreased it's value. + * @param list A storage pointer to the underlying list. + * @param group The original address. + * @param newValue New value that has to be smaller or equal than the previous one. + * @param loopLimit The max limit of loops that will be executed. + */ + function getLesserAndGreaterOfAddressThatDecreasedValue( + SortedLinkedList.List storage list, + address group, + uint256 newValue, + uint256 loopLimit + ) + public + view + returns (address previous, address next) + { + (, previous, next) = get(list, group); + while (previous != address(0) && loopLimit != 0 && newValue < getValue(list, previous)) { + next = previous; + (, previous,) = get(list, next); + loopLimit--; + } + if (loopLimit == 0) { + return (address(0), address(0)); + } + } + + function toBytes(address a) public pure returns (bytes32) { + return bytes32(uint256(uint160(a)) << 96); + } + + function toAddress(bytes32 b) public pure returns (address) { + return address(uint160(uint256(b) >> 96)); + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return exists Whether or not the key exists. + * @return previousKey Previous key. + * @return nextKey Next key. + */ + function get( + SortedLinkedList.List storage list, + address key + ) + internal + view + returns (bool exists, address previousKey, address nextKey) + { + LinkedList.Element memory element = list.get(toBytes(key)); + exists = element.exists; + if (element.exists) { + previousKey = toAddress(element.previousKey); + nextKey = toAddress(element.nextKey); + } + } +}
diff --git OP/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol CELO/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol new file mode 100644 index 0000000000000000000000000000000000000000..2ddf56612244e868dd6f3ba0aede4103c7569441 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "./SortedLinkedListWithMedian.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by address. + */ +library AddressSortedLinkedListWithMedian { + using SortedLinkedListWithMedian for SortedLinkedListWithMedian.List; + + function toBytes(address a) public pure returns (bytes32) { + return bytes32(uint256(uint160(a)) << 96); + } + + function toAddress(bytes32 b) public pure returns (address) { + return address(uint160(uint256(b) >> 96)); + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert( + SortedLinkedListWithMedian.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.insert(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(SortedLinkedListWithMedian.List storage list, address key) public { + list.remove(toBytes(key)); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update( + SortedLinkedListWithMedian.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.update(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(SortedLinkedListWithMedian.List storage list, address key) public view returns (bool) { + return list.contains(toBytes(key)); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(SortedLinkedListWithMedian.List storage list, address key) public view returns (uint256) { + return list.getValue(toBytes(key)); + } + + /** + * @notice Returns the median value of the sorted list. + * @param list A storage pointer to the underlying list. + * @return The median value. + */ + function getMedianValue(SortedLinkedListWithMedian.List storage list) public view returns (uint256) { + return list.getValue(list.median); + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getHead()); + } + + /** + * @notice Returns the key of the median element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the median element in the list. + */ + function getMedian(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getMedian()); + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getTail()); + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(SortedLinkedListWithMedian.List storage list) external view returns (uint256) { + return list.getNumElements(); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + * @return Array of relations to median of corresponding list elements. + */ + function getElements(SortedLinkedListWithMedian.List storage list) + public + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + bytes32[] memory byteKeys = list.getKeys(); + address[] memory keys = new address[](byteKeys.length); + uint256[] memory values = new uint256[](byteKeys.length); + // prettier-ignore + SortedLinkedListWithMedian.MedianRelation[] memory relations = + new SortedLinkedListWithMedian.MedianRelation[](keys.length); + for (uint256 i = 0; i < byteKeys.length; i++) { + keys[i] = toAddress(byteKeys[i]); + values[i] = list.getValue(byteKeys[i]); + relations[i] = list.relation[byteKeys[i]]; + } + return (keys, values, relations); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol CELO/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol new file mode 100644 index 0000000000000000000000000000000000000000..d04e8b7e027cbb3f8e665e07420b8ca16205d084 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +/** + * @title Maintains a doubly linked list keyed by bytes32. + * @dev Following the `next` pointers will lead you to the head, rather than the tail. + */ +library LinkedList { + struct Element { + bytes32 previousKey; + bytes32 nextKey; + bool exists; + } + + struct List { + bytes32 head; + bytes32 tail; + uint256 numElements; + mapping(bytes32 => Element) elements; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param previousKey The key of the element that comes before the element to insert. + * @param nextKey The key of the element that comes after the element to insert. + */ + function insert(List storage list, bytes32 key, bytes32 previousKey, bytes32 nextKey) internal { + require(key != bytes32(0), "Key must be defined"); + require(!contains(list, key), "Can't insert an existing element"); + require(previousKey != key && nextKey != key, "Key cannot be the same as previousKey or nextKey"); + + Element storage element = list.elements[key]; + element.exists = true; + + if (list.numElements == 0) { + list.tail = key; + list.head = key; + } else { + require(previousKey != bytes32(0) || nextKey != bytes32(0), "Either previousKey or nextKey must be defined"); + + element.previousKey = previousKey; + element.nextKey = nextKey; + + if (previousKey != bytes32(0)) { + require(contains(list, previousKey), "If previousKey is defined, it must exist in the list"); + Element storage previousElement = list.elements[previousKey]; + require(previousElement.nextKey == nextKey, "previousKey must be adjacent to nextKey"); + previousElement.nextKey = key; + } else { + list.tail = key; + } + + if (nextKey != bytes32(0)) { + require(contains(list, nextKey), "If nextKey is defined, it must exist in the list"); + Element storage nextElement = list.elements[nextKey]; + require(nextElement.previousKey == previousKey, "previousKey must be adjacent to nextKey"); + nextElement.previousKey = key; + } else { + list.head = key; + } + } + + list.numElements = list.numElements + 1; + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, bytes32(0), list.tail); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + Element storage element = list.elements[key]; + require(key != bytes32(0) && contains(list, key), "key not in list"); + if (element.previousKey != bytes32(0)) { + Element storage previousElement = list.elements[element.previousKey]; + previousElement.nextKey = element.nextKey; + } else { + list.tail = element.nextKey; + } + + if (element.nextKey != bytes32(0)) { + Element storage nextElement = list.elements[element.nextKey]; + nextElement.previousKey = element.previousKey; + } else { + list.head = element.previousKey; + } + + delete list.elements[key]; + list.numElements = list.numElements - 1; + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param previousKey The key of the element that comes before the updated element. + * @param nextKey The key of the element that comes after the updated element. + */ + function update(List storage list, bytes32 key, bytes32 previousKey, bytes32 nextKey) internal { + require(key != bytes32(0) && key != previousKey && key != nextKey && contains(list, key), "key on in list"); + remove(list, key); + insert(list, key, previousKey, nextKey); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.elements[key].exists; + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function get(List storage list, bytes32 key) internal view returns (Element memory) { + return list.elements[key]; + } + + /** + * @notice Returns the keys of the N elements at the head of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the N elements at the head of the list. + * @dev Reverts if n is greater than the number of elements in the list. + */ + function headN(List storage list, uint256 n) internal view returns (bytes32[] memory) { + require(n <= list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + bytes32 key = list.head; + for (uint256 i = 0; i < n; i = i + 1) { + keys[i] = key; + key = list.elements[key].previousKey; + } + return keys; + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return headN(list, list.numElements); + } +}
diff --git OP/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol CELO/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol new file mode 100644 index 0000000000000000000000000000000000000000..9703cf565523deace3374d6a334ecfdc7c0589eb --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "./LinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by bytes32. + */ +library SortedLinkedList { + using LinkedList for LinkedList.List; + + struct List { + LinkedList.List list; + mapping(bytes32 => uint256) values; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + require(key != bytes32(0) && key != lesserKey && key != greaterKey && !contains(list, key), "invalid key"); + require( + (lesserKey != bytes32(0) || greaterKey != bytes32(0)) || list.list.numElements == 0, + "greater and lesser key zero" + ); + require(contains(list, lesserKey) || lesserKey == bytes32(0), "invalid lesser key"); + require(contains(list, greaterKey) || greaterKey == bytes32(0), "invalid greater key"); + (lesserKey, greaterKey) = getLesserAndGreater(list, value, lesserKey, greaterKey); + list.list.insert(key, lesserKey, greaterKey); + list.values[key] = value; + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + list.list.remove(key); + list.values[key] = 0; + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + remove(list, key); + insert(list, key, value, lesserKey, greaterKey); + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, 0, bytes32(0), list.list.tail); + } + + /** + * @notice Removes N elements from the head of the list and returns their keys. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to pop. + * @return The keys of the popped elements. + */ + function popN(List storage list, uint256 n) internal returns (bytes32[] memory) { + require(n <= list.list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + for (uint256 i = 0; i < n; i = i + 1) { + bytes32 key = list.list.head; + keys[i] = key; + remove(list, key); + } + return keys; + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.list.contains(key); + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function get(List storage list, bytes32 key) internal view returns (LinkedList.Element memory) { + return list.list.get(key); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(List storage list, bytes32 key) internal view returns (uint256) { + return list.values[key]; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + */ + function getElements(List storage list) internal view returns (bytes32[] memory, uint256[] memory) { + bytes32[] memory keys = getKeys(list); + uint256[] memory values = new uint256[](keys.length); + for (uint256 i = 0; i < keys.length; i = i + 1) { + values[i] = list.values[keys[i]]; + } + return (keys, values); + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return list.list.getKeys(); + } + + /** + * @notice Returns first N greatest elements of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the first n elements. + * @dev Reverts if n is greater than the number of elements in the list. + */ + function headN(List storage list, uint256 n) internal view returns (bytes32[] memory) { + return list.list.headN(n); + } + + /** + * @notice Returns the keys of the elements greaterKey than and less than the provided value. + * @param list A storage pointer to the underlying list. + * @param value The element value. + * @param lesserKey The key of the element which could be just left of the new value. + * @param greaterKey The key of the element which could be just right of the new value. + * @return The correct lesserKey keys. + * @return The correct greaterKey keys. + */ + function getLesserAndGreater( + List storage list, + uint256 value, + bytes32 lesserKey, + bytes32 greaterKey + ) + private + view + returns (bytes32, bytes32) + { + // Check for one of the following conditions and fail if none are met: + // 1. The value is less than the current lowest value + // 2. The value is greater than the current greatest value + // 3. The value is just greater than the value for `lesserKey` + // 4. The value is just less than the value for `greaterKey` + if (lesserKey == bytes32(0) && isValueBetween(list, value, lesserKey, list.list.tail)) { + return (lesserKey, list.list.tail); + } else if (greaterKey == bytes32(0) && isValueBetween(list, value, list.list.head, greaterKey)) { + return (list.list.head, greaterKey); + } else if ( + lesserKey != bytes32(0) && isValueBetween(list, value, lesserKey, list.list.elements[lesserKey].nextKey) + ) { + return (lesserKey, list.list.elements[lesserKey].nextKey); + } else if ( + greaterKey != bytes32(0) + && isValueBetween(list, value, list.list.elements[greaterKey].previousKey, greaterKey) + ) { + return (list.list.elements[greaterKey].previousKey, greaterKey); + } + + require(false, "get lesser and greater failure"); + return (0, 0); + } + + /** + * @notice Returns whether or not a given element is between two other elements. + * @param list A storage pointer to the underlying list. + * @param value The element value. + * @param lesserKey The key of the element whose value should be lesserKey. + * @param greaterKey The key of the element whose value should be greaterKey. + * @return True if the given element is between the two other elements. + */ + function isValueBetween( + List storage list, + uint256 value, + bytes32 lesserKey, + bytes32 greaterKey + ) + private + view + returns (bool) + { + bool isLesser = lesserKey == bytes32(0) || list.values[lesserKey] <= value; + bool isGreater = greaterKey == bytes32(0) || list.values[greaterKey] >= value; + return isLesser && isGreater; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol CELO/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol new file mode 100644 index 0000000000000000000000000000000000000000..458ef554220772c97273526478a794885fa6ac45 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "./LinkedList.sol"; +import "./SortedLinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by bytes32. + */ +library SortedLinkedListWithMedian { + using SortedLinkedList for SortedLinkedList.List; + + enum MedianAction { + None, + Lesser, + Greater + } + + enum MedianRelation { + Undefined, + Lesser, + Greater, + Equal + } + + struct List { + SortedLinkedList.List list; + bytes32 median; + mapping(bytes32 => MedianRelation) relation; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + list.list.insert(key, value, lesserKey, greaterKey); + LinkedList.Element storage element = list.list.list.elements[key]; + + MedianAction action = MedianAction.None; + if (list.list.list.numElements == 1) { + list.median = key; + list.relation[key] = MedianRelation.Equal; + } else if (list.list.list.numElements % 2 == 1) { + // When we have an odd number of elements, and the element that we inserted is less than + // the previous median, we need to slide the median down one element, since we had previously + // selected the greater of the two middle elements. + if (element.previousKey == bytes32(0) || list.relation[element.previousKey] == MedianRelation.Lesser) { + action = MedianAction.Lesser; + list.relation[key] = MedianRelation.Lesser; + } else { + list.relation[key] = MedianRelation.Greater; + } + } else { + // When we have an even number of elements, and the element that we inserted is greater than + // the previous median, we need to slide the median up one element, since we always select + // the greater of the two middle elements. + if (element.nextKey == bytes32(0) || list.relation[element.nextKey] == MedianRelation.Greater) { + action = MedianAction.Greater; + list.relation[key] = MedianRelation.Greater; + } else { + list.relation[key] = MedianRelation.Lesser; + } + } + updateMedian(list, action); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + MedianAction action = MedianAction.None; + if (list.list.list.numElements == 0) { + list.median = bytes32(0); + } else if (list.list.list.numElements % 2 == 0) { + // When we have an even number of elements, we always choose the higher of the two medians. + // Thus, if the element we're removing is greaterKey than or equal to the median we need to + // slide the median left by one. + if (list.relation[key] == MedianRelation.Greater || list.relation[key] == MedianRelation.Equal) { + action = MedianAction.Lesser; + } + } else { + // When we don't have an even number of elements, we just choose the median value. + // Thus, if the element we're removing is less than or equal to the median, we need to slide + // median right by one. + if (list.relation[key] == MedianRelation.Lesser || list.relation[key] == MedianRelation.Equal) { + action = MedianAction.Greater; + } + } + updateMedian(list, action); + + list.list.remove(key); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + remove(list, key); + insert(list, key, value, lesserKey, greaterKey); + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, 0, bytes32(0), list.list.list.tail); + } + + /** + * @notice Removes N elements from the head of the list and returns their keys. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to pop. + * @return The keys of the popped elements. + */ + function popN(List storage list, uint256 n) internal returns (bytes32[] memory) { + require(n <= list.list.list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + for (uint256 i = 0; i < n; i++) { + bytes32 key = list.list.list.head; + keys[i] = key; + remove(list, key); + } + return keys; + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.list.contains(key); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(List storage list, bytes32 key) internal view returns (uint256) { + return list.list.values[key]; + } + + /** + * @notice Returns the median value of the sorted list. + * @param list A storage pointer to the underlying list. + * @return The median value. + */ + function getMedianValue(List storage list) internal view returns (uint256) { + return getValue(list, list.median); + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(List storage list) internal view returns (bytes32) { + return list.list.list.head; + } + + /** + * @notice Returns the key of the median element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the median element in the list. + */ + function getMedian(List storage list) internal view returns (bytes32) { + return list.median; + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(List storage list) internal view returns (bytes32) { + return list.list.list.tail; + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(List storage list) internal view returns (uint256) { + return list.list.list.numElements; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + * @return Array of relations to median of corresponding list elements. + */ + function getElements(List storage list) + internal + view + returns (bytes32[] memory, uint256[] memory, MedianRelation[] memory) + { + bytes32[] memory keys = getKeys(list); + uint256[] memory values = new uint256[](keys.length); + MedianRelation[] memory relations = new MedianRelation[](keys.length); + for (uint256 i = 0; i < keys.length; i++) { + values[i] = list.list.values[keys[i]]; + relations[i] = list.relation[keys[i]]; + } + return (keys, values, relations); + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return list.list.getKeys(); + } + + /** + * @notice Moves the median pointer right or left of its current value. + * @param list A storage pointer to the underlying list. + * @param action Which direction to move the median pointer. + */ + function updateMedian(List storage list, MedianAction action) private { + LinkedList.Element storage previousMedian = list.list.list.elements[list.median]; + if (action == MedianAction.Lesser) { + list.relation[list.median] = MedianRelation.Greater; + list.median = previousMedian.previousKey; + } else if (action == MedianAction.Greater) { + list.relation[list.median] = MedianRelation.Lesser; + list.median = previousMedian.nextKey; + } + list.relation[list.median] = MedianRelation.Equal; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol CELO/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol new file mode 100644 index 0000000000000000000000000000000000000000..f099ce364a2705987e0b242c07fbc1871371077b --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IElection { + function electValidatorSigners() external view returns (address[] memory); + function electNValidatorSigners(uint256, uint256) external view returns (address[] memory); + function vote(address, uint256, address, address) external returns (bool); + function activate(address) external returns (bool); + function revokeActive(address, uint256, address, address, uint256) external returns (bool); + function revokeAllActive(address, address, address, uint256) external returns (bool); + function revokePending(address, uint256, address, address, uint256) external returns (bool); + function markGroupIneligible(address) external; + function markGroupEligible(address, address, address) external; + function allowedToVoteOverMaxNumberOfGroups(address) external returns (bool); + function forceDecrementVotes( + address, + uint256, + address[] calldata, + address[] calldata, + uint256[] calldata + ) + external + returns (uint256); + function setAllowedToVoteOverMaxNumberOfGroups(bool flag) external; + + // view functions + function getElectableValidators() external view returns (uint256, uint256); + function getElectabilityThreshold() external view returns (uint256); + function getNumVotesReceivable(address) external view returns (uint256); + function getTotalVotes() external view returns (uint256); + function getActiveVotes() external view returns (uint256); + function getTotalVotesByAccount(address) external view returns (uint256); + function getPendingVotesForGroupByAccount(address, address) external view returns (uint256); + function getActiveVotesForGroupByAccount(address, address) external view returns (uint256); + function getTotalVotesForGroupByAccount(address, address) external view returns (uint256); + function getActiveVoteUnitsForGroupByAccount(address, address) external view returns (uint256); + function getTotalVotesForGroup(address) external view returns (uint256); + function getActiveVotesForGroup(address) external view returns (uint256); + function getPendingVotesForGroup(address) external view returns (uint256); + function getGroupEligibility(address) external view returns (bool); + function getGroupEpochRewards(address, uint256, uint256[] calldata) external view returns (uint256); + function getGroupsVotedForByAccount(address) external view returns (address[] memory); + function getEligibleValidatorGroups() external view returns (address[] memory); + function getTotalVotesForEligibleValidatorGroups() external view returns (address[] memory, uint256[] memory); + function getCurrentValidatorSigners() external view returns (address[] memory); + function canReceiveVotes(address, uint256) external view returns (bool); + function hasActivatablePendingVotes(address, address) external view returns (bool); + function validatorSignerAddressFromCurrentSet(uint256 index) external view returns (address); + function numberValidatorsInCurrentSet() external view returns (uint256); + + // only owner + function setElectableValidators(uint256, uint256) external returns (bool); + function setMaxNumGroupsVotedFor(uint256) external returns (bool); + function setElectabilityThreshold(uint256) external returns (bool); + + // only VM + function distributeEpochRewards(address, uint256, address, address) external; +}
diff --git OP/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol CELO/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol new file mode 100644 index 0000000000000000000000000000000000000000..883844ea8f219feaaa747a6fd61f33424c6828a8 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IGovernance { + function votePartially( + uint256 proposalId, + uint256 index, + uint256 yesVotes, + uint256 noVotes, + uint256 abstainVotes + ) + external + returns (bool); + + function isVoting(address) external view returns (bool); + function getAmountOfGoldUsedForVoting(address account) external view returns (uint256); + + function getProposal(uint256 proposalId) + external + view + returns (address, uint256, uint256, uint256, string memory, uint256, bool); + + function getReferendumStageDuration() external view returns (uint256); +}
diff --git OP/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol CELO/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol new file mode 100644 index 0000000000000000000000000000000000000000..38002d58914c70665df042ea9e3a3051d2c53091 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface ILockedGold { + function lock() external payable; + function incrementNonvotingAccountBalance(address, uint256) external; + function decrementNonvotingAccountBalance(address, uint256) external; + function getAccountTotalLockedGold(address) external view returns (uint256); + function getTotalLockedGold() external view returns (uint256); + function getPendingWithdrawals(address) external view returns (uint256[] memory, uint256[] memory); + function getPendingWithdrawal(address account, uint256 index) external view returns (uint256, uint256); + function getTotalPendingWithdrawals(address) external view returns (uint256); + function unlock(uint256) external; + function relock(uint256, uint256) external; + function withdraw(uint256) external; + function slash( + address account, + uint256 penalty, + address reporter, + uint256 reward, + address[] calldata lessers, + address[] calldata greaters, + uint256[] calldata indices + ) + external; + function isSlasher(address) external view returns (bool); + function unlockingPeriod() external view returns (uint256); + function getAccountNonvotingLockedGold(address account) external view returns (uint256); +}
diff --git OP/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol CELO/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol new file mode 100644 index 0000000000000000000000000000000000000000..e211ce7399e37478a43b3b5e0335339e5c253265 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IReleaseGold { + function transfer(address, uint256) external; + function unlockGold(uint256) external; + function withdrawLockedGold(uint256) external; + function authorizeVoteSigner(address payable, uint8, bytes32, bytes32) external; + function authorizeValidatorSigner(address payable, uint8, bytes32, bytes32) external; + function authorizeValidatorSignerWithPublicKey(address payable, uint8, bytes32, bytes32, bytes calldata) external; + function authorizeValidatorSignerWithKeys( + address payable, + uint8, + bytes32, + bytes32, + bytes calldata, + bytes calldata, + bytes calldata + ) + external; + function authorizeAttestationSigner(address payable, uint8, bytes32, bytes32) external; + function revokeActive(address, uint256, address, address, uint256) external; + function revokePending(address, uint256, address, address, uint256) external; + + // view functions + function getTotalBalance() external view returns (uint256); + function getRemainingTotalBalance() external view returns (uint256); + function getRemainingUnlockedBalance() external view returns (uint256); + function getRemainingLockedBalance() external view returns (uint256); + function getCurrentReleasedTotalAmount() external view returns (uint256); + function isRevoked() external view returns (bool); + + // only beneficiary + function setCanExpire(bool) external; + function withdraw(uint256) external; + function lockGold(uint256) external; + function relockGold(uint256, uint256) external; + function setAccount(string calldata, bytes calldata, address, uint8, bytes32, bytes32) external; + function createAccount() external; + function setAccountName(string calldata) external; + function setAccountWalletAddress(address, uint8, bytes32, bytes32) external; + function setAccountDataEncryptionKey(bytes calldata) external; + function setAccountMetadataURL(string calldata) external; + + // only owner + function setBeneficiary(address payable) external; + + // only release owner + function setLiquidityProvision() external; + function setMaxDistribution(uint256) external; + function refundAndFinalize() external; + function revoke() external; + function expire() external; +}
diff --git OP/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol CELO/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol new file mode 100644 index 0000000000000000000000000000000000000000..8a10e91fc8129cfb736057443bbec7c63170921e --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IValidators { + function registerValidator(bytes calldata, bytes calldata, bytes calldata) external returns (bool); + function deregisterValidator(uint256) external returns (bool); + function affiliate(address) external returns (bool); + function deaffiliate() external returns (bool); + function updateBlsPublicKey(bytes calldata, bytes calldata) external returns (bool); + function registerValidatorGroup(uint256) external returns (bool); + function deregisterValidatorGroup(uint256) external returns (bool); + function addMember(address) external returns (bool); + function addFirstMember(address, address, address) external returns (bool); + function removeMember(address) external returns (bool); + function reorderMember(address, address, address) external returns (bool); + function updateCommission() external; + function setNextCommissionUpdate(uint256) external; + function resetSlashingMultiplier() external; + + // only owner + function setCommissionUpdateDelay(uint256) external; + function setMaxGroupSize(uint256) external returns (bool); + function setMembershipHistoryLength(uint256) external returns (bool); + function setValidatorScoreParameters(uint256, uint256) external returns (bool); + function setGroupLockedGoldRequirements(uint256, uint256) external returns (bool); + function setValidatorLockedGoldRequirements(uint256, uint256) external returns (bool); + function setSlashingMultiplierResetPeriod(uint256) external; + + // view functions + function getMaxGroupSize() external view returns (uint256); + function getCommissionUpdateDelay() external view returns (uint256); + function getValidatorScoreParameters() external view returns (uint256, uint256); + function getMembershipHistory(address) + external + view + returns (uint256[] memory, address[] memory, uint256, uint256); + function calculateEpochScore(uint256) external view returns (uint256); + function calculateGroupEpochScore(uint256[] calldata) external view returns (uint256); + function getAccountLockedGoldRequirement(address) external view returns (uint256); + function meetsAccountLockedGoldRequirements(address) external view returns (bool); + function getValidatorBlsPublicKeyFromSigner(address) external view returns (bytes memory); + function getValidator(address account) + external + view + returns (bytes memory, bytes memory, address, uint256, address); + function getValidatorGroup(address) + external + view + returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256); + function getGroupNumMembers(address) external view returns (uint256); + function getTopGroupValidators(address, uint256) external view returns (address[] memory); + function getGroupsNumMembers(address[] calldata accounts) external view returns (uint256[] memory); + function getNumRegisteredValidators() external view returns (uint256); + function groupMembershipInEpoch(address, uint256, uint256) external view returns (address); + + // only registered contract + function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool); + function updatePublicKeys( + address, + address, + bytes calldata, + bytes calldata, + bytes calldata + ) + external + returns (bool); + function getValidatorLockedGoldRequirements() external view returns (uint256, uint256); + function getGroupLockedGoldRequirements() external view returns (uint256, uint256); + function getRegisteredValidators() external view returns (address[] memory); + function getRegisteredValidatorSigners() external view returns (address[] memory); + function getRegisteredValidatorGroups() external view returns (address[] memory); + function isValidatorGroup(address) external view returns (bool); + function isValidator(address) external view returns (bool); + function getValidatorGroupSlashingMultiplier(address) external view returns (uint256); + function getMembershipInLastEpoch(address) external view returns (address); + function getMembershipInLastEpochFromSigner(address) external view returns (address); + + // only VM + function updateValidatorScoreFromSigner(address, uint256) external; + function distributeEpochPaymentsFromSigner(address, uint256) external returns (uint256); + + // only slasher + function forceDeaffiliateIfValidator(address) external; + function halveSlashingMultiplier(address) external; +}
diff --git OP/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol new file mode 100644 index 0000000000000000000000000000000000000000..5c1a1d7a8f484e1cedc6336b39144ceea0941c97 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IAttestations { + function revoke(bytes32, uint256) external; + function withdraw(address) external; + + // view functions + function getUnselectedRequest(bytes32, address) external view returns (uint32, uint32, address); + function getAttestationIssuers(bytes32, address) external view returns (address[] memory); + function getAttestationStats(bytes32, address) external view returns (uint32, uint32); + function batchGetAttestationStats(bytes32[] calldata) + external + view + returns (uint256[] memory, address[] memory, uint64[] memory, uint64[] memory); + function getAttestationState(bytes32, address, address) external view returns (uint8, uint32, address); + function getCompletableAttestations( + bytes32, + address + ) + external + view + returns (uint32[] memory, address[] memory, uint256[] memory, bytes memory); + function getAttestationRequestFee(address) external view returns (uint256); + function getMaxAttestations() external view returns (uint256); + function validateAttestationCode(bytes32, address, uint8, bytes32, bytes32) external view returns (address); + function lookupAccountsForIdentifier(bytes32) external view returns (address[] memory); + function requireNAttestationsRequested(bytes32, address, uint32) external view; + + // only owner + function setAttestationRequestFee(address, uint256) external; + function setAttestationExpiryBlocks(uint256) external; + function setSelectIssuersWaitBlocks(uint256) external; + function setMaxAttestations(uint256) external; +}
diff --git OP/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol new file mode 100644 index 0000000000000000000000000000000000000000..87c145a4a1bb9bf3aa469bb8ec5ca6e92073525f --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface IEscrow { + function transfer( + bytes32 identifier, + address token, + uint256 value, + uint256 expirySeconds, + address paymentId, + uint256 minAttestations + ) + external + returns (bool); + function transferWithTrustedIssuers( + bytes32 identifier, + address token, + uint256 value, + uint256 expirySeconds, + address paymentId, + uint256 minAttestations, + address[] calldata trustedIssuers + ) + external + returns (bool); + function withdraw(address paymentID, uint8 v, bytes32 r, bytes32 s) external returns (bool); + function revoke(address paymentID) external returns (bool); + + // view functions + function getReceivedPaymentIds(bytes32 identifier) external view returns (address[] memory); + function getSentPaymentIds(address sender) external view returns (address[] memory); + function getTrustedIssuersPerPayment(address paymentId) external view returns (address[] memory); + function getDefaultTrustedIssuers() external view returns (address[] memory); + function MAX_TRUSTED_ISSUERS_PER_PAYMENT() external view returns (uint256); + + // onlyOwner functions + function addDefaultTrustedIssuer(address trustedIssuer) external; + function removeDefaultTrustedIssuer(address trustedIssuer, uint256 index) external; +}
diff --git OP/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol new file mode 100644 index 0000000000000000000000000000000000000000..c0586eb9e44dc7a10fdd0e86de09a7753c6e4974 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface IFederatedAttestations { + function registerAttestationAsIssuer(bytes32 identifier, address account, uint64 issuedOn) external; + function registerAttestation( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn, + uint8 v, + bytes32 r, + bytes32 s + ) + external; + function revokeAttestation(bytes32 identifier, address issuer, address account) external; + function batchRevokeAttestations( + address issuer, + bytes32[] calldata identifiers, + address[] calldata accounts + ) + external; + + // view functions + function lookupAttestations( + bytes32 identifier, + address[] calldata trustedIssuers + ) + external + view + returns (uint256[] memory, address[] memory, address[] memory, uint64[] memory, uint64[] memory); + function lookupIdentifiers( + address account, + address[] calldata trustedIssuers + ) + external + view + returns (uint256[] memory, bytes32[] memory); + function validateAttestationSig( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn, + uint8 v, + bytes32 r, + bytes32 s + ) + external + view; + function getUniqueAttestationHash( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn + ) + external + pure + returns (bytes32); +}
diff --git OP/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol new file mode 100644 index 0000000000000000000000000000000000000000..ca188432c0dda414dfa563d857cccc600947de9e --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface IOdisPayments { + function payInCUSD(address account, uint256 value) external; + function totalPaidCUSD(address) external view returns (uint256); +}
diff --git OP/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol new file mode 100644 index 0000000000000000000000000000000000000000..65cf3082d685cd4a5a9a2e38d8254e85de1bb2e5 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IRandom { + function revealAndCommit(bytes32, bytes32, address) external; + function randomnessBlockRetentionWindow() external view returns (uint256); + function random() external view returns (bytes32); + function getBlockRandomness(uint256) external view returns (bytes32); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol CELO/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol new file mode 100644 index 0000000000000000000000000000000000000000..734dcddeb941d89c467af92a24ba0a488df4ab57 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IAccounts { + function isAccount(address) external view returns (bool); + function voteSignerToAccount(address) external view returns (address); + function validatorSignerToAccount(address) external view returns (address); + function attestationSignerToAccount(address) external view returns (address); + function signerToAccount(address) external view returns (address); + function getAttestationSigner(address) external view returns (address); + function getValidatorSigner(address) external view returns (address); + function getVoteSigner(address) external view returns (address); + function hasAuthorizedVoteSigner(address) external view returns (bool); + function hasAuthorizedValidatorSigner(address) external view returns (bool); + function hasAuthorizedAttestationSigner(address) external view returns (bool); + + function setAccountDataEncryptionKey(bytes calldata) external; + function setMetadataURL(string calldata) external; + function setName(string calldata) external; + function setWalletAddress(address, uint8, bytes32, bytes32) external; + function setAccount(string calldata, bytes calldata, address, uint8, bytes32, bytes32) external; + + function getDataEncryptionKey(address) external view returns (bytes memory); + function getWalletAddress(address) external view returns (address); + function getMetadataURL(address) external view returns (string memory); + function batchGetMetadataURL(address[] calldata) external view returns (uint256[] memory, bytes memory); + function getName(address) external view returns (string memory); + + function authorizeVoteSigner(address, uint8, bytes32, bytes32) external; + function authorizeValidatorSigner(address, uint8, bytes32, bytes32) external; + function authorizeValidatorSignerWithPublicKey(address, uint8, bytes32, bytes32, bytes calldata) external; + function authorizeValidatorSignerWithKeys( + address, + uint8, + bytes32, + bytes32, + bytes calldata, + bytes calldata, + bytes calldata + ) + external; + function authorizeAttestationSigner(address, uint8, bytes32, bytes32) external; + function createAccount() external returns (bool); + + function setPaymentDelegation(address, uint256) external; + function getPaymentDelegation(address) external view returns (address, uint256); + function isSigner(address, address, bytes32) external view returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol CELO/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol new file mode 100644 index 0000000000000000000000000000000000000000..95e586da3954ffc48f18c7e781161990abab7936 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface ICeloRegistry { + function setAddressFor(string calldata, address) external; + function getAddressForOrDie(bytes32) external view returns (address); + function getAddressFor(bytes32) external view returns (address); + function getAddressForStringOrDie(string calldata identifier) external view returns (address); + function getAddressForString(string calldata identifier) external view returns (address); + function isOneOf(bytes32[] calldata, address) external view returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/ICeloToken copy.sol CELO/packages/contracts-bedrock/src/celo/interfaces/ICeloToken copy.sol new file mode 100644 index 0000000000000000000000000000000000000000..5bf2033f31726110e6504078561108cfee40a42d --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/ICeloToken copy.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the non- ERC20 shared interface for all Celo Tokens, and + * in the absence of interface inheritance is intended as a companion to IERC20.sol. + */ +interface ICeloToken { + function transferWithComment(address, uint256, string calldata) external returns (bool); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function burn(uint256 value) external returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol CELO/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol new file mode 100644 index 0000000000000000000000000000000000000000..5bf2033f31726110e6504078561108cfee40a42d --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the non- ERC20 shared interface for all Celo Tokens, and + * in the absence of interface inheritance is intended as a companion to IERC20.sol. + */ +interface ICeloToken { + function transferWithComment(address, uint256, string calldata) external returns (bool); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function burn(uint256 value) external returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol CELO/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol new file mode 100644 index 0000000000000000000000000000000000000000..37b1538c2a121b4dd73b5762db9ba4a97364581c --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface ICeloVersionedContract { + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol CELO/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol new file mode 100644 index 0000000000000000000000000000000000000000..5c6ab9051ccf28eebd9f8482ae22b9e9df8e939f --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IFeeCurrencyDirectory { + struct CurrencyConfig { + address oracle; + uint256 intrinsicGas; + } + + /** + * @notice Returns the list of all currency addresses. + * @return An array of addresses. + */ + function getCurrencies() external view returns (address[] memory); + /** + * @notice Returns the configuration for a currency. + * @param token The address of the token. + * @return Currency configuration of the token. + */ + function getCurrencyConfig(address token) external view returns (CurrencyConfig memory); + + /** + * @notice Retrieves exchange rate between token and CELO. + * @param token The token address whose price is to be fetched. + * @return numerator The exchange rate numerator. + * @return denominator The exchange rate denominator. + */ + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol CELO/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol new file mode 100644 index 0000000000000000000000000000000000000000..a629b3325a5ba883298aa61d85f260573fe042a8 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IFreezer { + function isFrozen(address) external view returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol CELO/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol new file mode 100644 index 0000000000000000000000000000000000000000..5c7f392814b615b6b35ec6de7e4a044708fec8df --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IMetaTransactionWallet { + function setEip712DomainSeparator() external; + function executeMetaTransaction( + address, + uint256, + bytes calldata, + uint8, + bytes32, + bytes32 + ) + external + returns (bytes memory); + function executeTransaction(address, uint256, bytes calldata) external returns (bytes memory); + function executeTransactions( + address[] calldata, + uint256[] calldata, + bytes calldata, + uint256[] calldata + ) + external + returns (bytes memory, uint256[] memory); + + // view functions + function getMetaTransactionDigest(address, uint256, bytes calldata, uint256) external view returns (bytes32); + function getMetaTransactionSigner( + address, + uint256, + bytes calldata, + uint256, + uint8, + bytes32, + bytes32 + ) + external + view + returns (address); + + //only owner + function setSigner(address) external; +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol CELO/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol new file mode 100644 index 0000000000000000000000000000000000000000..5828bee3c7467f9386974465a6a3fee00c3ef65f --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IMetaTransactionWalletDeployer { + function deploy(address, address, bytes calldata) external; +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol CELO/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol new file mode 100644 index 0000000000000000000000000000000000000000..b3ae66a92756c915ede5a6c5b1f57387b2edc254 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.0; + +/// Possibly not final version +interface IOracle { + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol CELO/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol new file mode 100644 index 0000000000000000000000000000000000000000..b13febff81fc8c95da5bc8567fdbcc64938c9d19 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.5.17 <9.0.0; + +interface IStableTokenV2 { + function totalSupply() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function transfer(address recipient, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) + external; + + /** + * @notice Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + * @param comment The transfer comment. + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool); + + /** + * @notice Initializes a StableTokenV2. + * It keeps the same signature as the original initialize() function + * in legacy/StableToken.sol + * @param _name The name of the stable token (English) + * @param _symbol A short symbol identifying the token (e.g. "cUSD") + * @param initialBalanceAddresses Array of addresses with an initial balance. + * @param initialBalanceValues Array of balance values corresponding to initialBalanceAddresses. + * deprecated-param exchangeIdentifier String identifier of exchange in registry (for specific fiat pairs) + */ + function initialize( + string calldata _name, + string calldata _symbol, + address[] calldata initialBalanceAddresses, + uint256[] calldata initialBalanceValues + ) + external; + + /** + * @notice Initializes a StableTokenV2 contract + * when upgrading from legacy/StableToken.sol. + * It sets the addresses that were previously read from the Registry. + * It runs the ERC20PermitUpgradeable initializer. + * @dev This function is only callable once. + * @param _broker The address of the Broker contract. + * @param _validators The address of the Validators contract. + * @param _exchange The address of the Exchange contract. + */ + function initializeV2(address _broker, address _validators, address _exchange) external; + + /** + * @notice Gets the address of the Broker contract. + */ + function broker() external returns (address); + + /** + * @notice Gets the address of the Validators contract. + */ + function validators() external returns (address); + + /** + * @notice Gets the address of the Exchange contract. + */ + function exchange() external returns (address); + + function debitGasFees(address from, uint256 value) external; + + function creditGasFees( + address from, + address feeRecipient, + address gatewayFeeRecipient, + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256 gatewayFee, + uint256 baseTxFee + ) + external; +}
diff --git OP/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol CELO/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol new file mode 100644 index 0000000000000000000000000000000000000000..b309071d9f0ad88f27ece5e2648d4fa51465b741 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the functions specific to Celo Stable Tokens, and in the + * absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol. + */ +interface IStableTokenMento { + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function setInflationParameters(uint256, uint256) external; + + function valueToUnits(uint256) external view returns (uint256); + + function unitsToValue(uint256) external view returns (uint256); + + function getInflationParameters() external view returns (uint256, uint256, uint256, uint256); + + // NOTE: duplicated with IERC20.sol, remove once interface inheritance is supported. + function balanceOf(address) external view returns (uint256); + + function getExchangeRegistryId() external view returns (bytes32); + + function approve(address spender, uint256 value) external returns (bool); +}
diff --git OP/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol CELO/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol new file mode 100644 index 0000000000000000000000000000000000000000..4e15e8a8750d45e28c6fc9eb7e483bf39591fa0b --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IExchange { + function buy(uint256, uint256, bool) external returns (uint256); + + function sell(uint256, uint256, bool) external returns (uint256); + + function exchange(uint256, uint256, bool) external returns (uint256); + + function setUpdateFrequency(uint256) external; + + function getBuyTokenAmount(uint256, bool) external view returns (uint256); + + function getSellTokenAmount(uint256, bool) external view returns (uint256); + + function getBuyAndSellBuckets(bool) external view returns (uint256, uint256); +}
diff --git OP/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol CELO/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol new file mode 100644 index 0000000000000000000000000000000000000000..14f77c10549a1247f645191e241cd61a23145af5 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IReserve { + function setTobinTaxStalenessThreshold(uint256) external; + + function addToken(address) external returns (bool); + + function removeToken(address, uint256) external returns (bool); + + function transferGold(address payable, uint256) external returns (bool); + + function transferExchangeGold(address payable, uint256) external returns (bool); + + function getReserveGoldBalance() external view returns (uint256); + + function getUnfrozenReserveGoldBalance() external view returns (uint256); + + function getOrComputeTobinTax() external returns (uint256, uint256); + + function getTokens() external view returns (address[] memory); + + function getReserveRatio() external view returns (uint256); + + function addExchangeSpender(address) external; + + function removeExchangeSpender(address, uint256) external; + + function addSpender(address) external; + + function removeSpender(address) external; +}
diff --git OP/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol CELO/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol new file mode 100644 index 0000000000000000000000000000000000000000..c0b681dfb8aee25aa686d5484adea6f3f6f79179 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @title This interface describes the functions specific to Celo Stable Tokens, and in the + * absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol. + */ +interface IStableToken { + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function setInflationParameters(uint256, uint256) external; + + function valueToUnits(uint256) external view returns (uint256); + + function unitsToValue(uint256) external view returns (uint256); + + function getInflationParameters() external view returns (uint256, uint256, uint256, uint256); + + // NOTE: duplicated with IERC20.sol, remove once interface inheritance is supported. + function balanceOf(address) external view returns (uint256); +}
diff --git OP/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol CELO/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol new file mode 100644 index 0000000000000000000000000000000000000000..d2209dac5d2c8de4df0d0a1eb0ae0ec3f0e4422b --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.15; + +import "../../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../../lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; + +import "./interfaces/ISortedOracles.sol"; +import "../common/interfaces/ICeloVersionedContract.sol"; +import "./interfaces/IBreakerBox.sol"; + +import "../common/FixidityLib.sol"; +import "../common/Initializable.sol"; +import "../common/linkedlists/AddressSortedLinkedListWithMedian.sol"; +import "../common/linkedlists/SortedLinkedListWithMedian.sol"; +import "./interfaces/IOracle.sol"; + +/** + * @title SortedOracles + * + * @notice This contract stores a collection of exchange rates with CELO + * expressed in units of other assets. The most recent exchange rates + * are gathered off-chain by oracles, who then use the `report` function to + * submit the rates to this contract. Before submitting a rate report, an + * oracle's address must be added to the `isOracle` mapping for a specific + * rateFeedId, with the flag set to true. While submitting a report requires + * an address to be added to the mapping, no additional permissions are needed + * to read the reports, the calculated median rate, or the list of oracles. + * + * @dev A unique rateFeedId identifies each exchange rate. In the initial implementation + * of this contract, the rateFeedId was set as the address of the stable + * asset contract that used the rate. However, this implementation has since + * been updated, and the rateFeedId block.timestamp also refers to an address derived from the + * concatenation other asset symbols. This change enables the contract to store multiple exchange rates for a + * single token. As a result of this change, there may be instances + * where the term "token" is used in the contract code. These useages of the term + * "token" are actually referring to the rateFeedId. + * + */ +contract SortedOracles is ISortedOracles, IOracle, ICeloVersionedContract, Ownable, Initializable { + using SafeMath for uint256; + using AddressSortedLinkedListWithMedian for SortedLinkedListWithMedian.List; + using FixidityLib for FixidityLib.Fraction; + + struct EquivalentToken { + address token; + } + + uint256 private constant FIXED1_UINT = 1e24; + + // Maps a rateFeedID to a sorted list of report values. + mapping(address => SortedLinkedListWithMedian.List) private rates; + // Maps a rateFeedID to a sorted list of report timestamps. + mapping(address => SortedLinkedListWithMedian.List) private timestamps; + mapping(address => mapping(address => bool)) public isOracle; + mapping(address => address[]) public oracles; + + // `reportExpirySeconds` is the fallback value used to determine reporting + // frequency. Initially it was the _only_ value but we later introduced + // the per token mapping in `tokenReportExpirySeconds`. If a token + // doesn't have a value in the mapping (i.e. it's 0), the fallback is used. + // See: #getTokenReportExpirySeconds + uint256 public reportExpirySeconds; + // Maps a rateFeedId to its report expiry time in seconds. + mapping(address => uint256) public tokenReportExpirySeconds; + + IBreakerBox public breakerBox; + // Maps a token address to its equivalent token address. + // Original token will return the median value same as the value of equivalent token. + mapping(address => EquivalentToken) public equivalentTokens; + + event OracleAdded(address indexed token, address indexed oracleAddress); + event OracleRemoved(address indexed token, address indexed oracleAddress); + event OracleReported(address indexed token, address indexed oracle, uint256 timestamp, uint256 value); + event OracleReportRemoved(address indexed token, address indexed oracle); + event MedianUpdated(address indexed token, uint256 value); + event ReportExpirySet(uint256 reportExpiry); + event TokenReportExpirySet(address token, uint256 reportExpiry); + event BreakerBoxUpdated(address indexed newBreakerBox); + event EquivalentTokenSet(address indexed token, address indexed equivalentToken); + + modifier onlyOracle(address token) { + require(isOracle[token][msg.sender], "sender was not an oracle for token addr"); + _; + } + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function initialize(uint256 _reportExpirySeconds) external initializer { + _transferOwnership(msg.sender); + setReportExpiry(_reportExpirySeconds); + } + + /** + * @notice Sets the report expiry parameter for a rateFeedId. + * @param _token The token for which the report expiry is being set. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function setTokenReportExpiry(address _token, uint256 _reportExpirySeconds) external onlyOwner { + require(_reportExpirySeconds > 0, "report expiry seconds must be > 0"); + require(_reportExpirySeconds != tokenReportExpirySeconds[_token], "token reportExpirySeconds hasn't changed"); + tokenReportExpirySeconds[_token] = _reportExpirySeconds; + emit TokenReportExpirySet(_token, _reportExpirySeconds); + } + + /** + * @notice Adds a new Oracle for a specified rate feed. + * @param token The token for which the specified oracle is to be added. + * @param oracleAddress The address of the oracle. + */ + function addOracle(address token, address oracleAddress) external onlyOwner { + // solhint-disable-next-line reason-string + require( + token != address(0) && oracleAddress != address(0) && !isOracle[token][oracleAddress], + "token addr was null or oracle addr was null or oracle addr is already an oracle for token addr" + ); + isOracle[token][oracleAddress] = true; + oracles[token].push(oracleAddress); + emit OracleAdded(token, oracleAddress); + } + + /** + * @notice Removes an Oracle from a specified rate feed. + * @param token The token from which the specified oracle is to be removed. + * @param oracleAddress The address of the oracle. + * @param index The index of `oracleAddress` in the list of oracles. + */ + function removeOracle(address token, address oracleAddress, uint256 index) external onlyOwner { + // solhint-disable-next-line reason-string + require( + token != address(0) && oracleAddress != address(0) && oracles[token].length > index + && oracles[token][index] == oracleAddress, + "token addr null or oracle addr null or index of token oracle not mapped to oracle addr" + ); + isOracle[token][oracleAddress] = false; + oracles[token][index] = oracles[token][oracles[token].length.sub(1)]; + oracles[token].pop(); + if (reportExists(token, oracleAddress)) { + removeReport(token, oracleAddress); + } + emit OracleRemoved(token, oracleAddress); + } + + /** + * @notice Removes a report that is expired. + * @param token The token for which the expired report is to be removed. + * @param n The number of expired reports to remove, at most (deterministic upper gas bound). + */ + function removeExpiredReports(address token, uint256 n) external { + require( + token != address(0) && n < timestamps[token].getNumElements(), + "token addr null or trying to remove too many reports" + ); + for (uint256 i = 0; i < n; i = i.add(1)) { + (bool isExpired, address oldestAddress) = isOldestReportExpired(token); + if (isExpired) { + removeReport(token, oldestAddress); + } else { + break; + } + } + } + + /** + * @notice Sets the equivalent token for a token. + * @param token The address of the token. + * @param equivalentToken The address of the equivalent token. + */ + function setEquivalentToken(address token, address equivalentToken) external onlyOwner { + require(token != address(0), "token address cannot be 0"); + require(equivalentToken != address(0), "equivalentToken address cannot be 0"); + equivalentTokens[token] = EquivalentToken(equivalentToken); + emit EquivalentTokenSet(token, equivalentToken); + } + + /** + * @notice Sets the equivalent token for a token. + * @param token The address of the token. + */ + function deleteEquivalentToken(address token) external onlyOwner { + require(token != address(0), "token address cannot be 0"); + delete equivalentTokens[token]; + emit EquivalentTokenSet(token, address(0)); + } + + /** + * @notice Updates an oracle value and the median. + * @param token The token for which the rate is being reported. + * @param value The number of stable asset that equate to one unit of collateral asset, for the + * specified rateFeedId, expressed as a fixidity value. + * @param lesserKey The element which should be just left of the new oracle value. + * @param greaterKey The element which should be just right of the new oracle value. + * @dev Note that only one of `lesserKey` or `greaterKey` needs to be correct to reduce friction. + */ + function report(address token, uint256 value, address lesserKey, address greaterKey) external onlyOracle(token) { + uint256 originalMedian = rates[token].getMedianValue(); + if (rates[token].contains(msg.sender)) { + rates[token].update(msg.sender, value, lesserKey, greaterKey); + + // Rather than update the timestamp, we remove it and re-add it at the + // head of the list later. The reason for this is that we need to handle + // a few different cases: + // 1. This oracle is the only one to report so far. lesserKey = address(0) + // 2. Other oracles have reported since this one's last report. lesserKey = getHead() + // 3. Other oracles have reported, but the most recent is this one. + // lesserKey = key immediately after getHead() + // + // However, if we just remove this timestamp, timestamps[token].getHead() + // does the right thing in all cases. + timestamps[token].remove(msg.sender); + } else { + rates[token].insert(msg.sender, value, lesserKey, greaterKey); + } + timestamps[token].insert( + msg.sender, + // solhint-disable-next-line not-rely-on-time + block.timestamp, + timestamps[token].getHead(), + address(0) + ); + emit OracleReported(token, msg.sender, block.timestamp, value); + uint256 newMedian = rates[token].getMedianValue(); + if (newMedian != originalMedian) { + emit MedianUpdated(token, newMedian); + } + + if (address(breakerBox) != address(0)) { + breakerBox.checkAndSetBreakers(token); + } + } + + /** + * @notice Gets the equivalent token for a token. + * @param token The address of the token. + * @return The address of the equivalent token. + */ + function getEquivalentToken(address token) external view returns (address) { + return (equivalentTokens[token].token); + } + + /** + * @notice Returns the median timestamp. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the median timestamp is being retrieved. + * @return uint256 The median report timestamp for the specified rateFeedId. + */ + function medianTimestamp(address token) external view returns (uint256) { + return timestamps[token].getMedianValue(); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the timestamps are being retrieved. + * @return keys Keys of nn unpacked list of elements from largest to smallest. + * @return values Values of an unpacked list of elements from largest to smallest. + * @return relations Relations of an unpacked list of elements from largest to smallest. + */ + function getTimestamps(address token) + external + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + return timestamps[token].getElements(); + } + + /** + * @notice Returns the list of oracles for a speficied rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the oracles are being retrieved. + * @return address[] A list of oracles for the given rateFeedId. + */ + function getOracles(address token) external view returns (address[] memory) { + return oracles[token]; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the rates are being retrieved. + * @return keys Keys of an unpacked list of elements from largest to smallest. + * @return values Values of an unpacked list of elements from largest to smallest. + * @return relations Relations of an unpacked list of elements from largest to smallest. + */ + function getRates(address token) + external + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + return rates[token].getElements(); + } + + /** + * @notice Returns the exchange rate for a specified token. + * @param token The token for which the exchange rate is being retrieved. + * @return numerator uint256 The exchange rate for the specified token. + * @return denominator uint256 The denominator for the exchange rate. + */ + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator) { + (numerator, denominator) = medianRate(token); + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 4, 0); + } + + /** + * @notice Sets the report expiry parameter. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function setReportExpiry(uint256 _reportExpirySeconds) public onlyOwner { + require(_reportExpirySeconds > 0, "report expiry seconds must be > 0"); + require(_reportExpirySeconds != reportExpirySeconds, "reportExpirySeconds hasn't changed"); + reportExpirySeconds = _reportExpirySeconds; + emit ReportExpirySet(_reportExpirySeconds); + } + + /** + * @notice Sets the address of the BreakerBox. + * @param newBreakerBox The new BreakerBox address. + */ + function setBreakerBox(IBreakerBox newBreakerBox) public onlyOwner { + require(address(newBreakerBox) != address(0), "BreakerBox address must be set"); + breakerBox = newBreakerBox; + emit BreakerBoxUpdated(address(newBreakerBox)); + } + + /** + * @notice Returns the median of the currently stored rates for a specified rateFeedId. + * @dev Please note that this function respects the equivalentToken mapping, and so may + * return the median identified as an equivalent to the supplied rateFeedId. + * @param token The token for which the median value is being retrieved. + * @return uint256 The median exchange rate for rateFeedId (fixidity). + * @return uint256 denominator + */ + function medianRate(address token) public view returns (uint256, uint256) { + EquivalentToken storage equivalentToken = equivalentTokens[token]; + if (equivalentToken.token != address(0)) { + (uint256 equivalentMedianRate, uint256 denominator) = + medianRateWithoutEquivalentMapping(equivalentToken.token); + return (equivalentMedianRate, denominator); + } + + return medianRateWithoutEquivalentMapping(token); + } + + /** + * @notice Returns the number of rates that are currently stored for a specifed rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the number of rates is being retrieved. + * @return uint256 The number of reported oracle rates stored for the given rateFeedId. + */ + function numRates(address token) public view returns (uint256) { + return rates[token].getNumElements(); + } + + /** + * @notice Check if last report is expired. + * @param token The token for which the expired report is to be checked. + * @return bool A bool indicating if the last report is expired. + * @return address Oracle address of the last report. + */ + function isOldestReportExpired(address token) public view returns (bool, address) { + // solhint-disable-next-line reason-string + require(token != address(0)); + address oldest = timestamps[token].getTail(); + uint256 timestamp = timestamps[token].getValue(oldest); + // solhint-disable-next-line not-rely-on-time + if (block.timestamp.sub(timestamp) >= getTokenReportExpirySeconds(token)) { + return (true, oldest); + } + return (false, oldest); + } + + /** + * @notice Returns the median of the currently stored rates for a specified rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * @param token The token for which the median value is being retrieved. + * @return uint256 The median exchange rate for rateFeedId (fixidity). + * @return uint256 denominator + */ + function medianRateWithoutEquivalentMapping(address token) public view returns (uint256, uint256) { + return (rates[token].getMedianValue(), numRates(token) == 0 ? 0 : FIXED1_UINT); + } + + /** + * @notice Returns the number of timestamps. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the number of timestamps is being retrieved. + * @return uint256 The number of oracle report timestamps for the specified rateFeedId. + */ + function numTimestamps(address token) public view returns (uint256) { + return timestamps[token].getNumElements(); + } + + /** + * @notice Returns the expiry for specified rateFeedId if it exists, if not the default is returned. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the report expiry is being retrieved. + * @return The report expiry in seconds. + */ + function getTokenReportExpirySeconds(address token) public view returns (uint256) { + if (tokenReportExpirySeconds[token] == 0) { + return reportExpirySeconds; + } + + return tokenReportExpirySeconds[token]; + } + + /** + * @notice Checks if a report exists for a specified rateFeedId from a given oracle. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the report should be checked. + * @param oracle The oracle whose report should be checked. + * @return bool True if a report exists, false otherwise. + */ + function reportExists(address token, address oracle) internal view returns (bool) { + return rates[token].contains(oracle) && timestamps[token].contains(oracle); + } + + /** + * @notice Removes an oracle value and updates the median. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the oracle report should be removed. + * @param oracle The oracle whose value should be removed. + * @dev This can be used to delete elements for oracles that have been removed. + * However, a > 1 elements reports list should always be maintained + */ + function removeReport(address token, address oracle) private { + if (numTimestamps(token) == 1 && reportExists(token, oracle)) return; + uint256 originalMedian = rates[token].getMedianValue(); + rates[token].remove(oracle); + timestamps[token].remove(oracle); + emit OracleReportRemoved(token, oracle); + uint256 newMedian = rates[token].getMedianValue(); + if (newMedian != originalMedian) { + emit MedianUpdated(token, newMedian); + if (address(breakerBox) != address(0)) { + breakerBox.checkAndSetBreakers(token); + } + } + } +}
diff --git OP/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol CELO/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol new file mode 100644 index 0000000000000000000000000000000000000000..26430da7a3bea5db65b04502a7057a0606b44d7d --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.0; + +/** + * @title Breaker Box Interface + * @notice Defines the basic interface for the Breaker Box + */ +interface IBreakerBox { + /** + * @dev Used to keep track of the status of a breaker for a specific rate feed. + * + * - TradingMode: Represents the trading mode the breaker is in for a rate feed. + * This uses a bitmask approach, meaning each bit represents a + * different trading mode. The final trading mode of the rate feed + * is obtained by applying a logical OR operation to the TradingMode + * of all breakers associated with that rate feed. This allows multiple + * breakers to contribute to the final trading mode simultaneously. + * Possible values: + * 0: bidirectional trading. + * 1: inflow only. + * 2: outflow only. + * 3: trading halted. + * + * - LastUpdatedTime: Records the last time the breaker status was updated. This is + * used to manage cooldown periods before the breaker can be reset. + * + * - Enabled: Indicates whether the breaker is enabled for the associated rate feed. + */ + struct BreakerStatus { + uint8 tradingMode; + uint64 lastUpdatedTime; + bool enabled; + } + + /** + * @notice Emitted when a new breaker is added to the breaker box. + * @param breaker The address of the breaker. + */ + event BreakerAdded(address indexed breaker); + + /** + * @notice Emitted when a breaker is removed from the breaker box. + * @param breaker The address of the breaker. + */ + event BreakerRemoved(address indexed breaker); + + /** + * @notice Emitted when a breaker is tripped by a rate feed. + * @param breaker The address of the breaker. + * @param rateFeedID The address of the rate feed. + */ + event BreakerTripped(address indexed breaker, address indexed rateFeedID); + + /** + * @notice Emitted when a new rate feed is added to the breaker box. + * @param rateFeedID The address of the rate feed. + */ + event RateFeedAdded(address indexed rateFeedID); + + /** + * @notice Emitted when dependencies for a rate feed are set. + * @param rateFeedID The address of the rate feed. + * @param dependencies The addresses of the dependendent rate feeds. + */ + event RateFeedDependenciesSet(address indexed rateFeedID, address[] indexed dependencies); + + /** + * @notice Emitted when a rate feed is removed from the breaker box. + * @param rateFeedID The address of the rate feed. + */ + event RateFeedRemoved(address indexed rateFeedID); + + /** + * @notice Emitted when the trading mode for a rate feed is updated + * @param rateFeedID The address of the rate feed. + * @param tradingMode The new trading mode. + */ + event TradingModeUpdated(address indexed rateFeedID, uint256 tradingMode); + + /** + * @notice Emitted after a reset attempt is successful. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetSuccessful(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted after a reset attempt fails when the + * rate feed fails the breakers reset criteria. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetAttemptCriteriaFail(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted after a reset attempt fails when cooldown time has not elapsed. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetAttemptNotCool(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted when the sortedOracles address is updated. + * @param newSortedOracles The address of the new sortedOracles. + */ + event SortedOraclesUpdated(address indexed newSortedOracles); + + /** + * @notice Emitted when the breaker is enabled or disabled for a rate feed. + * @param breaker The address of the breaker. + * @param rateFeedID The address of the rate feed. + * @param status Indicating the status. + */ + event BreakerStatusUpdated(address breaker, address rateFeedID, bool status); + + /** + * @notice Checks breakers for the rateFeedID and sets correct trading mode + * if any breakers are tripped or need to be reset. + * @param rateFeedID The address of the rate feed to run checks for. + */ + function checkAndSetBreakers(address rateFeedID) external; + + /** + * @notice Retrives an array of all breaker addresses. + */ + function getBreakers() external view returns (address[] memory); + + /** + * @notice Checks if a breaker with the specified address has been added to the breaker box. + * @param breaker The address of the breaker to check; + * @return A bool indicating whether or not the breaker has been added. + */ + function isBreaker(address breaker) external view returns (bool); + + /** + * @notice Gets the trading mode for the specified rateFeedID. + * @param rateFeedID The address of the rate feed to retrieve the trading mode for. + */ + function getRateFeedTradingMode(address rateFeedID) external view returns (uint8 tradingMode); +}
diff --git OP/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol CELO/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol new file mode 100644 index 0000000000000000000000000000000000000000..b3ae66a92756c915ede5a6c5b1f57387b2edc254 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.0; + +/// Possibly not final version +interface IOracle { + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +}
diff --git OP/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol CELO/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol new file mode 100644 index 0000000000000000000000000000000000000000..ecea4210cd40e4fb48a7a101b74625ff9746edcc --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface ISortedOracles { + function addOracle(address, address) external; + function removeOracle(address, address, uint256) external; + function report(address, uint256, address, address) external; + function removeExpiredReports(address, uint256) external; + function isOldestReportExpired(address token) external view returns (bool, address); + function numRates(address) external view returns (uint256); + function medianRate(address) external view returns (uint256, uint256); + function numTimestamps(address) external view returns (uint256); + function medianTimestamp(address) external view returns (uint256); +}
diff --git OP/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol CELO/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol new file mode 100644 index 0000000000000000000000000000000000000000..fd00f42c01bbb45a455790ec9e7cd3d181742319 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: MIT +// Modified from OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +import "../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "../../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "../../../lib/openzeppelin-contracts/contracts/utils/Context.sol"; + +import "../CalledByVm.sol"; + +/** + * @dev Implementation of the {IERC20} interface + Celo debit/creditGasFees. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract FeeCurrency is Context, IERC20, IERC20Metadata, CalledByVm { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer(address from, address to, uint256 amount) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** + * @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + /** + * @notice Reserve balance for making payments for gas in this StableToken currency. + * @param from The account to reserve balance from + * @param value The amount of balance to reserve + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. After the tx is executed, gas is refunded to the sender and credited to the + * various tx fee recipients via a call to `creditGasFees`. Note too that the events emitted + * by `creditGasFees` reflect the *net* gas fee payments for the transaction. + */ + function debitGasFees(address from, uint256 value) external onlyVm { + _balances[from] -= value; + _totalSupply -= value; + } + + /** + * @notice Alternative function to credit balance after making payments + * for gas in this StableToken currency. + * @param from The account to debit balance from + * @param feeRecipient Coinbase address + * legacy param gatewayFeeRecipient Gateway address (UNUSED!) + * @param communityFund Community fund address + * @param tipTxFee Coinbase fee + * @param baseTxFee Community fund fee + * legacy param gatewayFee Gateway fee (UNUSED!) + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. Before the tx is executed, gas is debited from the sender via a call to + * `debitGasFees`. Note too that the events emitted by `creditGasFees` reflect the *net* gas fee + * payments for the transaction. + */ + function creditGasFees( + address from, + address feeRecipient, + address, // gatewayFeeRecipient + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256, // gatewayFee + uint256 baseTxFee + ) + external + onlyVm + { + _balances[from] += refund; + + refund += _creditGas(from, communityFund, baseTxFee); + refund += _creditGas(from, feeRecipient, tipTxFee); + _totalSupply += refund; + } + + function _creditGas(address from, address to, uint256 value) internal returns (uint256) { + if (to == address(0)) { + return 0; + } + _balances[to] += value; + emit Transfer(from, to, value); + return value; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol CELO/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol new file mode 100644 index 0000000000000000000000000000000000000000..d51fa2a7c56c47dc97af8fd34c1db22b8832e7ec --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { IOracle } from "../interfaces/IOracle.sol"; + +/** + * @title A mock SortedOracles for testing. + */ +contract MockSortedOracles is IOracle { + uint256 public constant DENOMINATOR = 1000000000000000000000000; + mapping(address => uint256) public numerators; + mapping(address => uint256) public medianTimestamp; + mapping(address => uint256) public numRates; + mapping(address => bool) public expired; + + function setMedianRate(address token, uint256 numerator) external returns (bool) { + numerators[token] = numerator; + return true; + } + + function setMedianTimestamp(address token, uint256 timestamp) external { + medianTimestamp[token] = timestamp; + } + + function setMedianTimestampToNow(address token) external { + // solhint-disable-next-line not-rely-on-time + medianTimestamp[token] = uint128(block.timestamp); + } + + function setNumRates(address token, uint256 rate) external { + numRates[token] = rate; // This change may break something, TODO + } + + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator) { + return medianRate(token); + } + + function medianRate(address token) public view returns (uint256, uint256) { + if (numerators[token] > 0) { + return (numerators[token], DENOMINATOR); + } + return (0, 0); + } + + function isOldestReportExpired(address token) public view returns (bool, address) { + return (expired[token], token); + } + + function setOldestReportExpired(address token) public { + expired[token] = true; + } +}
diff --git OP/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol CELO/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol new file mode 100644 index 0000000000000000000000000000000000000000..14c6495920a1ff49978917c124c7a7fd82d7c6b6 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IUniswapV2FactoryMin { + function getPair(address tokenA, address tokenB) external view returns (address pair); +}
diff --git OP/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol CELO/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol new file mode 100644 index 0000000000000000000000000000000000000000..f1755edb137d00b91696baa96ac6d44ae601ca28 --- /dev/null +++ CELO/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IUniswapV2RouterMin { + function factory() external pure returns (address); + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) + external + returns (uint256[] memory amounts); + function getAmountsOut( + uint256 amountIn, + address[] calldata path + ) + external + view + returns (uint256[] memory amounts); +}
diff --git OP/packages/contracts-bedrock/src/libraries/rlp/RLPErrors.sol CELO/packages/contracts-bedrock/src/libraries/rlp/RLPErrors.sol index f7a9cdb091c494402cf1a378ca7a00172349bd78..c2ce80a6cc735a9b0be82bf20da001557d625a40 100644 --- OP/packages/contracts-bedrock/src/libraries/rlp/RLPErrors.sol +++ CELO/packages/contracts-bedrock/src/libraries/rlp/RLPErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0;   /// @notice The length of an RLP item must be greater than zero to be decodable error EmptyItem();
diff --git OP/packages/contracts-bedrock/test/kontrol/deployment/KontrolDeployment.sol CELO/packages/contracts-bedrock/test/kontrol/deployment/KontrolDeployment.sol index 4b8ef355727c3436079e541468edbb08c1af925a..bb3117bad5fff8a27b28ca9430fa25c4d6cbad8a 100644 --- OP/packages/contracts-bedrock/test/kontrol/deployment/KontrolDeployment.sol +++ CELO/packages/contracts-bedrock/test/kontrol/deployment/KontrolDeployment.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;   -import { Deploy } from "scripts/Deploy.s.sol"; +import { Deploy } from "scripts/deploy/Deploy.s.sol";   contract KontrolDeployment is Deploy { function runKontrolDeployment() public stateDiff {
diff --git OP/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummary.sol CELO/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummary.sol index d8fbd28656a230476adcc3e15f8da8b862b486c3..45397d2c34f556dff2161a9211b05fc77c52e717 100644 --- OP/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummary.sol +++ CELO/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummary.sol @@ -21,7 +21,7 @@ address internal constant l1StandardBridgeAddress = 0xb7900B27Be8f0E0fF65d1C3A4671e1220437dd2b; address internal constant l1StandardBridgeProxyAddress = 0x0c8b5822b6e02CDa722174F19A1439A7495a3fA6; address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60; address internal constant l2OutputOracleProxyAddress = 0x8B71b41D4dBEb2b6821d44692d3fACAAf77480Bb; - address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131; + address internal constant optimismPortalAddress = 0xA4c07622d72648913221A0512Bdb95D0387ebe3B; address internal constant optimismPortalProxyAddress = 0x978e3286EB805934215a88694d80b09aDed68D90; address internal constant protocolVersionsAddress = 0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F; address internal constant protocolVersionsProxyAddress = 0x416C42991d05b31E9A6dC209e91AD22b79D87Ae6; @@ -440,7 +440,7 @@ slot = hex"0000000000000000000000000000000000000000000000000000000000000005"; value = hex"000000000000000000000000000000000000000000000000000000000000000a"; vm.store(systemOwnerSafeAddress, slot, value); slot = hex"360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; - value = hex"000000000000000000000000bdd90485fcbcac869d5b5752179815a3103d8131"; + value = hex"000000000000000000000000a4c07622d72648913221a0512bdb95d0387ebe3b"; vm.store(optimismPortalProxyAddress, slot, value); slot = hex"0000000000000000000000000000000000000000000000000000000000000000"; value = hex"0000000000000000000000000000000000000000000000000000000000000001";
diff --git OP/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryCode.sol CELO/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryCode.sol index 2f5208f8d2a8fd1ffe0781473ab94892af94cf67..a6d85beaa9865181bbf2e52aa81ae2097909063b 100644 --- OP/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryCode.sol +++ CELO/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryCode.sol @@ -35,7 +35,7 @@ hex"608060408181523060009081526001602090815282822054908290529181207fbf40fac1000000000000000000000000000000000000000000000000000000009093529173ffffffffffffffffffffffffffffffffffffffff9091169063bf40fac19061006d9060846101e2565b602060405180830381865afa15801561008a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ae91906102c5565b905073ffffffffffffffffffffffffffffffffffffffff8116610157576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f5265736f6c76656444656c656761746550726f78793a2074617267657420616460448201527f6472657373206d75737420626520696e697469616c697a656400000000000000606482015260840160405180910390fd5b6000808273ffffffffffffffffffffffffffffffffffffffff16600036604051610182929190610302565b600060405180830381855af49150503d80600081146101bd576040519150601f19603f3d011682016040523d82523d6000602084013e6101c2565b606091505b5090925090508115156001036101da57805160208201f35b805160208201fd5b600060208083526000845481600182811c91508083168061020457607f831692505b858310810361023a577f4e487b710000000000000000000000000000000000000000000000000000000085526022600452602485fd5b878601838152602001818015610257576001811461028b576102b6565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008616825284151560051b820196506102b6565b60008b81526020902060005b868110156102b057815484820152908501908901610297565b83019750505b50949998505050505050505050565b6000602082840312156102d757600080fd5b815173ffffffffffffffffffffffffffffffffffffffff811681146102fb57600080fd5b9392505050565b818382376000910190815291905056fea164736f6c634300080f000a"; bytes internal constant l1ERC721BridgeProxyCode = hex"60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106dd565b610224565b6100a86100a33660046106f8565b610296565b6040516100b5919061077b565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106dd565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ee565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060c565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81815560405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a25050565b60006106367fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038381556040805173ffffffffffffffffffffffffffffffffffffffff80851682528616602082015292935090917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d857600080fd5b919050565b6000602082840312156106ef57600080fd5b610412826106b4565b60008060006040848603121561070d57600080fd5b610716846106b4565b9250602084013567ffffffffffffffff8082111561073357600080fd5b818601915086601f83011261074757600080fd5b81358181111561075657600080fd5b87602082850101111561076857600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a85785810183015185820160400152820161078c565b818111156107ba576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a"; bytes internal constant optimismPortalCode = - hex"6080604052600436106101635760003560e01c80638c3152e9116100c0578063b69ef8a811610074578063cff0ab9611610059578063cff0ab9614610444578063e965084c146104e5578063e9e05c421461057157600080fd5b8063b69ef8a814610401578063c0c53b8b1461042457600080fd5b80639bf62d82116100a55780639bf62d821461036b578063a14238e714610398578063a35d99df146103c857600080fd5b80638c3152e91461031e5780639b5f694a1461033e57600080fd5b806354fd4d50116101175780636dbffb78116100fc5780636dbffb78146102de57806371cfaa3f146102fe5780638b4c40b01461018857600080fd5b806354fd4d501461026d5780635c975abb146102b957600080fd5b806335e80ab31161014857806335e80ab314610206578063452a9320146102385780634870496f1461024d57600080fd5b8063149f2f221461018f57806333d7e2bd146101af57600080fd5b3661018a576101883334620186a060006040518060200160405280600081525061057f565b005b600080fd5b34801561019b57600080fd5b506101886101aa366004614b97565b610624565b3480156101bb57600080fd5b506037546101dc9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561021257600080fd5b506035546101dc90610100900473ffffffffffffffffffffffffffffffffffffffff1681565b34801561024457600080fd5b506101dc610865565b34801561025957600080fd5b50610188610268366004614ccb565b6108fd565b34801561027957600080fd5b50604080518082018252600c81527f322e382e312d626574612e310000000000000000000000000000000000000000602082015290516101fd9190614e1d565b3480156102c557600080fd5b506102ce610eaa565b60405190151581526020016101fd565b3480156102ea57600080fd5b506102ce6102f9366004614e30565b610f3d565b34801561030a57600080fd5b50610188610319366004614e58565b610ff8565b34801561032a57600080fd5b50610188610339366004614e9e565b6111ba565b34801561034a57600080fd5b506036546101dc9073ffffffffffffffffffffffffffffffffffffffff1681565b34801561037757600080fd5b506032546101dc9073ffffffffffffffffffffffffffffffffffffffff1681565b3480156103a457600080fd5b506102ce6103b3366004614e30565b60336020526000908152604090205460ff1681565b3480156103d457600080fd5b506103e86103e3366004614edb565b611c3c565b60405167ffffffffffffffff90911681526020016101fd565b34801561040d57600080fd5b50610416611c55565b6040519081526020016101fd565b34801561043057600080fd5b5061018861043f366004614ef6565b611caf565b34801561045057600080fd5b506001546104ac906fffffffffffffffffffffffffffffffff81169067ffffffffffffffff7001000000000000000000000000000000008204811691780100000000000000000000000000000000000000000000000090041683565b604080516fffffffffffffffffffffffffffffffff909416845267ffffffffffffffff92831660208501529116908201526060016101fd565b3480156104f157600080fd5b50610543610500366004614e30565b603460205260009081526040902080546001909101546fffffffffffffffffffffffffffffffff8082169170010000000000000000000000000000000090041683565b604080519384526fffffffffffffffffffffffffffffffff92831660208501529116908201526060016101fd565b61018861057f366004614f41565b8260005a9050600061058f611f19565b50905073ffffffffffffffffffffffffffffffffffffffff811673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee148015906105cb57503415155b15610602576040517ff2365b5b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610610883489898989611fb6565b5061061b8282612162565b50505050505050565b8260005a90506000610634611f19565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8216016106a6576040517f0eaf3c0f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b87603d60008282546106b89190614fed565b90915550506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa15801561072a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074e9190615005565b905061077273ffffffffffffffffffffffffffffffffffffffff831633308c61242f565b61077c8982614fed565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8416906370a0823190602401602060405180830381865afa1580156107e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061080a9190615005565b14610841576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61084f8a8a8a8a8a8a611fb6565b505061085b8282612162565b5050505050505050565b6000603560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663452a93206040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f8919061501e565b905090565b610905610eaa565b1561093c576040517ff480973e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff16856040015173ffffffffffffffffffffffffffffffffffffffff16036109a5576040517f13496fda00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6036546040517fa25ae5570000000000000000000000000000000000000000000000000000000081526004810186905260009173ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa158015610a15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a39919061505b565b519050610a53610a4e368690038601866150c0565b61250b565b8114610ae6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4f7074696d69736d506f7274616c3a20696e76616c6964206f7574707574207260448201527f6f6f742070726f6f66000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b6000610af187612567565b6000818152603460209081526040918290208251606081018452815481526001909101546fffffffffffffffffffffffffffffffff8082169383018490527001000000000000000000000000000000009091041692810192909252919250901580610c075750805160365460408084015190517fa25ae5570000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff9091169063a25ae55790602401606060405180830381865afa158015610bdf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c03919061505b565b5114155b610c93576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173682060448201527f68617320616c7265616479206265656e2070726f76656e0000000000000000006064820152608401610add565b60408051602081018490526000918101829052606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201209083018190529250610d5c9101604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828201909152600182527f0100000000000000000000000000000000000000000000000000000000000000602083015290610d52888a615126565b8a60400135612597565b610de8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4f7074696d69736d506f7274616c3a20696e76616c696420776974686472617760448201527f616c20696e636c7573696f6e2070726f6f6600000000000000000000000000006064820152608401610add565b604080516060810182528581526fffffffffffffffffffffffffffffffff42811660208084019182528c831684860190815260008981526034835286812095518655925190518416700100000000000000000000000000000000029316929092176001909301929092558b830151908c0151925173ffffffffffffffffffffffffffffffffffffffff918216939091169186917f67a6208cfcc0801d50f6cbe764733f4fddf66ac0b04442061a8a8c0cb6b63f629190a4505050505050505050565b6000603560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f19573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f891906151aa565b6036546040517fa25ae55700000000000000000000000000000000000000000000000000000000815260048101839052600091610ff29173ffffffffffffffffffffffffffffffffffffffff9091169063a25ae55790602401606060405180830381865afa158015610fb3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd7919061505b565b602001516fffffffffffffffffffffffffffffffff166125bb565b92915050565b60375473ffffffffffffffffffffffffffffffffffffffff163314611049576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61105562030d40612661565b60405173ffffffffffffffffffffffffffffffffffffffff8516602482015260ff8416604482015260648101839052608481018290526000907342000000000000000000000000000000000000159073deaddeaddeaddeaddeaddeaddeaddeaddead0001907fb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32908490819062030d4090829060a401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152602080830180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f71cfaa3f000000000000000000000000000000000000000000000000000000001790529051611172969594939291016151c7565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290526111aa91614e1d565b60405180910390a450505050565b565b6111c2610eaa565b156111f9576040517ff480973e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60325473ffffffffffffffffffffffffffffffffffffffff1661dead1461124c576040517f9396d15600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061125782612567565b60008181526034602090815260408083208151606081018352815481526001909101546fffffffffffffffffffffffffffffffff80821694830185905270010000000000000000000000000000000090910416918101919091529293509003611342576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173206e60448201527f6f74206265656e2070726f76656e2079657400000000000000000000000000006064820152608401610add565b603660009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663887862726040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113af573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113d39190615005565b81602001516fffffffffffffffffffffffffffffffff16101561149e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604b60248201527f4f7074696d69736d506f7274616c3a207769746864726177616c2074696d657360448201527f74616d70206c657373207468616e204c32204f7261636c65207374617274696e60648201527f672074696d657374616d70000000000000000000000000000000000000000000608482015260a401610add565b6114bd81602001516fffffffffffffffffffffffffffffffff166125bb565b61156f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604560248201527f4f7074696d69736d506f7274616c3a2070726f76656e2077697468647261776160448201527f6c2066696e616c697a6174696f6e20706572696f6420686173206e6f7420656c60648201527f6170736564000000000000000000000000000000000000000000000000000000608482015260a401610add565b60365460408281015190517fa25ae5570000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff909116600482015260009173ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa1580156115f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061161a919061505b565b82518151919250146116d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604960248201527f4f7074696d69736d506f7274616c3a206f757470757420726f6f742070726f7660448201527f656e206973206e6f74207468652073616d652061732063757272656e74206f7560648201527f7470757420726f6f740000000000000000000000000000000000000000000000608482015260a401610add565b6116f381602001516fffffffffffffffffffffffffffffffff166125bb565b6117a5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4f7074696d69736d506f7274616c3a206f75747075742070726f706f73616c2060448201527f66696e616c697a6174696f6e20706572696f6420686173206e6f7420656c617060648201527f7365640000000000000000000000000000000000000000000000000000000000608482015260a401610add565b60008381526033602052604090205460ff1615611844576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173206160448201527f6c7265616479206265656e2066696e616c697a656400000000000000000000006064820152608401610add565b6000838152603360209081526040822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558501516032805473ffffffffffffffffffffffffffffffffffffffff9092167fffffffffffffffffffffffff0000000000000000000000000000000000000000909216919091179055806118cf611f19565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8216016119325761192b8660400151876080015188606001518960a001516126c3565b9150611b85565b8073ffffffffffffffffffffffffffffffffffffffff16866040015173ffffffffffffffffffffffffffffffffffffffff160361199b576040517f13496fda00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606086015115611b5c578560600151603d60008282546119bb919061522c565b90915550506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa158015611a2d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a519190615005565b9050611a86876040015188606001518473ffffffffffffffffffffffffffffffffffffffff166127219092919063ffffffff16565b6060870151611a95908261522c565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8416906370a0823190602401602060405180830381865afa158015611aff573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b239190615005565b14611b5a576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b60a08601515115611b805761192b8660400151876080015160008960a001516126c3565b600191505b603280547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead17905560405185907fdb5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1b90611be790851515815260200190565b60405180910390a281158015611bfd5750326001145b15611c34576040517feeae4ed300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050565b6000611c49826010615243565b610ff290615208615273565b600080611c60611f19565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff821601611ca7574791505090565b5050603d5490565b600054610100900460ff1615808015611ccf5750600054600160ff909116105b80611ce95750303b158015611ce9575060005460ff166001145b611d75576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610add565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015611dd357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b603680547fffffffffffffffffffffffff000000000000000000000000000000000000000090811673ffffffffffffffffffffffffffffffffffffffff8781169190911790925560378054909116858316179055603580547fffffffffffffffffffffff0000000000000000000000000000000000000000ff166101008584160217905560325416611e8c57603280547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790555b611e9461277c565b8015611ef757600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b603754604080517f4397dfef0000000000000000000000000000000000000000000000000000000081528151600093849373ffffffffffffffffffffffffffffffffffffffff90911692634397dfef92600480830193928290030181865afa158015611f89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fad919061529f565b90939092509050565b818015611fd8575073ffffffffffffffffffffffffffffffffffffffff861615155b1561200f576040517f13496fda00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6120198151611c3c565b67ffffffffffffffff168367ffffffffffffffff161015612066576040517f4929b80800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6201d4c0815111156120a4576040517f73052b0f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b333281146120c5575033731111000000000000000000000000000000001111015b600086868686866040516020016120e09594939291906151c7565b604051602081830303815290604052905060008873ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32846040516121509190614e1d565b60405180910390a45050505050505050565b600154600090612198907801000000000000000000000000000000000000000000000000900467ffffffffffffffff164361522c565b905060006121a461288f565b90506000816020015160ff16826000015163ffffffff166121c59190615308565b905082156122fc576001546000906121fc908390700100000000000000000000000000000000900467ffffffffffffffff16615370565b90506000836040015160ff168361221391906153e4565b6001546122339084906fffffffffffffffffffffffffffffffff166153e4565b61223d9190615308565b60015490915060009061228e906122679084906fffffffffffffffffffffffffffffffff166154a0565b866060015163ffffffff168760a001516fffffffffffffffffffffffffffffffff16612950565b905060018611156122bd576122ba61226782876040015160ff1660018a6122b5919061522c565b61296f565b90505b6fffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000067ffffffffffffffff4316021760015550505b6001805486919060109061232f908490700100000000000000000000000000000000900467ffffffffffffffff16615273565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550816000015163ffffffff16600160000160109054906101000a900467ffffffffffffffff1667ffffffffffffffff1613156123bc576040517f77ebef4d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001546000906123e8906fffffffffffffffffffffffffffffffff1667ffffffffffffffff8816615514565b905060006123fa48633b9aca006129c4565b6124049083615551565b905060005a612413908861522c565b90508082111561085b5761085b61242a828461522c565b6129db565b60405173ffffffffffffffffffffffffffffffffffffffff80851660248301528316604482015260648101829052611ef79085907f23b872dd00000000000000000000000000000000000000000000000000000000906084015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612a04565b6000816000015182602001518360400151846060015160405160200161254a949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b80516020808301516040808501516060860151608087015160a0880151935160009761254a979096959101615565565b6000806125a386612b10565b90506125b181868686612b42565b9695505050505050565b603654604080517ff4daa291000000000000000000000000000000000000000000000000000000008152905160009273ffffffffffffffffffffffffffffffffffffffff169163f4daa2919160048083019260209291908290030181865afa15801561262b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061264f9190615005565b6126599083614fed565b421192915050565b6001805463ffffffff8316919060109061269a908490700100000000000000000000000000000000900467ffffffffffffffff16615273565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555050565b60008060006126d3866000612b72565b905080612709576308c379a06000526020805278185361666543616c6c3a204e6f7420656e6f756768206761736058526064601cfd5b600080855160208701888b5af1979650505050505050565b60405173ffffffffffffffffffffffffffffffffffffffff83166024820152604481018290526127779084907fa9059cbb0000000000000000000000000000000000000000000000000000000090606401612489565b505050565b600054610100900460ff16612813576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610add565b6001547801000000000000000000000000000000000000000000000000900467ffffffffffffffff166000036111b85760408051606081018252633b9aca00808252600060208301524367ffffffffffffffff169190920181905278010000000000000000000000000000000000000000000000000217600155565b6040805160c08082018352600080835260208301819052828401819052606083018190526080830181905260a083015260375483517fcc731b020000000000000000000000000000000000000000000000000000000081529351929373ffffffffffffffffffffffffffffffffffffffff9091169263cc731b02926004808401939192918290030181865afa15801561292c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f891906155d0565b600061296561295f8585612b90565b83612ba0565b90505b9392505050565b6000670de0b6b3a76400006129b06129878583615308565b61299990670de0b6b3a7640000615370565b6129ab85670de0b6b3a76400006153e4565b612baf565b6129ba90866153e4565b6129659190615308565b6000818310156129d45781612968565b5090919050565b6000805a90505b825a6129ee908361522c565b1015612777576129fd82615673565b91506129e2565b6000612a66826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16612be09092919063ffffffff16565b8051909150156127775780806020019051810190612a8491906151aa565b612777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610add565b60608180519060200120604051602001612b2c91815260200190565b6040516020818303038152906040529050919050565b6000612b6984612b53878686612bef565b8051602091820120825192909101919091201490565b95945050505050565b600080603f83619c4001026040850201603f5a021015949350505050565b6000818312156129d45781612968565b60008183126129d45781612968565b6000612968670de0b6b3a764000083612bc78661366d565b612bd191906153e4565b612bdb9190615308565b6138b1565b60606129658484600085613af0565b60606000845111612c5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4d65726b6c65547269653a20656d707479206b657900000000000000000000006044820152606401610add565b6000612c6784613c86565b90506000612c7486613d72565b9050600084604051602001612c8b91815260200190565b60405160208183030381529060405290506000805b84518110156135e4576000858281518110612cbd57612cbd6156ab565b602002602001015190508451831115612d58576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4d65726b6c65547269653a206b657920696e646578206578636565647320746f60448201527f74616c206b6579206c656e6774680000000000000000000000000000000000006064820152608401610add565b82600003612e115780518051602091820120604051612da692612d8092910190815260200190565b604051602081830303815290604052858051602091820120825192909101919091201490565b612e0c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4d65726b6c65547269653a20696e76616c696420726f6f7420686173680000006044820152606401610add565b612f68565b805151602011612ec75780518051602091820120604051612e3b92612d8092910190815260200190565b612e0c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d65726b6c65547269653a20696e76616c6964206c6172676520696e7465726e60448201527f616c2068617368000000000000000000000000000000000000000000000000006064820152608401610add565b805184516020808701919091208251919092012014612f68576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4d65726b6c65547269653a20696e76616c696420696e7465726e616c206e6f6460448201527f65206861736800000000000000000000000000000000000000000000000000006064820152608401610add565b612f7460106001614fed565b8160200151510361315057845183036130e857612fae8160200151601081518110612fa157612fa16156ab565b6020026020010151613dd5565b96506000875111613041576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286272616e63682900000000006064820152608401610add565b6001865161304f919061522c565b82146130dd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286272616e6368290000000000006064820152608401610add565b505050505050612968565b60008584815181106130fc576130fc6156ab565b602001015160f81c60f81b60f81c9050600082602001518260ff1681518110613127576131276156ab565b6020026020010151905061313a81613e89565b9550613147600186614fed565b945050506135d1565b60028160200151510361354957600061316882613eae565b905060008160008151811061317f5761317f6156ab565b016020015160f81c905060006131966002836156da565b6131a19060026156fc565b905060006131b2848360ff16613ed2565b905060006131c08a89613ed2565b905060006131ce8383613f08565b905080835114613260576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4d65726b6c65547269653a20706174682072656d61696e646572206d7573742060448201527f736861726520616c6c206e6962626c65732077697468206b65790000000000006064820152608401610add565b60ff851660021480613275575060ff85166003145b15613464578082511461330a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603d60248201527f4d65726b6c65547269653a206b65792072656d61696e646572206d757374206260448201527f65206964656e746963616c20746f20706174682072656d61696e6465720000006064820152608401610add565b6133248760200151600181518110612fa157612fa16156ab565b9c5060008d51116133b7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286c65616629000000000000006064820152608401610add565b60018c516133c5919061522c565b8814613453576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286c6561662900000000000000006064820152608401610add565b505050505050505050505050612968565b60ff85161580613477575060ff85166001145b156134b6576134a38760200151600181518110613496576134966156ab565b6020026020010151613e89565b99506134af818a614fed565b985061353e565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4d65726b6c65547269653a2072656365697665642061206e6f6465207769746860448201527f20616e20756e6b6e6f776e2070726566697800000000000000000000000000006064820152608401610add565b5050505050506135d1565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f4d65726b6c65547269653a20726563656976656420616e20756e70617273656160448201527f626c65206e6f64650000000000000000000000000000000000000000000000006064820152608401610add565b50806135dc81615673565b915050612ca0565b506040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4d65726b6c65547269653a2072616e206f7574206f662070726f6f6620656c6560448201527f6d656e74730000000000000000000000000000000000000000000000000000006064820152608401610add565b60008082136136d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600960248201527f554e444546494e454400000000000000000000000000000000000000000000006044820152606401610add565b600060606136e584613fbc565b03609f8181039490941b90931c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506027d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b393909302929092017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d92915050565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdb731c958f34d94c182136138e257506000919050565b680755bf798b4a1bf1e58212613954576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f4558505f4f564552464c4f5700000000000000000000000000000000000000006044820152606401610add565b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b606082471015613b82576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610add565b73ffffffffffffffffffffffffffffffffffffffff85163b613c00576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610add565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051613c29919061571f565b60006040518083038185875af1925050503d8060008114613c66576040519150601f19603f3d011682016040523d82523d6000602084013e613c6b565b606091505b5091509150613c7b828286614092565b979650505050505050565b80516060908067ffffffffffffffff811115613ca457613ca4614a8b565b604051908082528060200260200182016040528015613ce957816020015b6040805180820190915260608082526020820152815260200190600190039081613cc25790505b50915060005b81811015613d6b576040518060400160405280858381518110613d1457613d146156ab565b60200260200101518152602001613d43868481518110613d3657613d366156ab565b60200260200101516140e5565b815250838281518110613d5857613d586156ab565b6020908102919091010152600101613cef565b5050919050565b606080604051905082518060011b603f8101601f1916830160405280835250602084016020830160005b83811015613dca578060011b82018184015160001a8060041c8253600f811660018301535050600101613d9c565b509295945050505050565b60606000806000613de5856140f8565b919450925090506000816001811115613e0057613e0061573b565b14613e37576040517f1ff9b2e400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613e418284614fed565b855114613e7a576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612b6985602001518484614596565b60606020826000015110613ea557613ea082613dd5565b610ff2565b610ff28261462a565b6060610ff2613ecd8360200151600081518110612fa157612fa16156ab565b613d72565b606082518210613ef15750604080516020810190915260008152610ff2565b6129688383848651613f03919061522c565b614640565b6000808251845110613f1b578251613f1e565b83515b90505b8082108015613fa55750828281518110613f3d57613f3d6156ab565b602001015160f81c60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916848381518110613f7c57613f7c6156ab565b01602001517fff0000000000000000000000000000000000000000000000000000000000000016145b15613fb557816001019150613f21565b5092915050565b6000808211614027576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600960248201527f554e444546494e454400000000000000000000000000000000000000000000006044820152606401610add565b5060016fffffffffffffffffffffffffffffffff821160071b82811c67ffffffffffffffff1060061b1782811c63ffffffff1060051b1782811c61ffff1060041b1782811c60ff10600390811b90911783811c600f1060021b1783811c909110821b1791821c111790565b606083156140a1575081612968565b8251156140b15782518084602001fd5b816040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9190614e1d565b6060610ff26140f383614818565b614885565b6000806000836000015160000361413b576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020840151805160001a607f811161416057600060016000945094509450505061458f565b60b7811161427657600061417560808361522c565b9050808760000151116141b4576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001838101517fff0000000000000000000000000000000000000000000000000000000000000016908214801561422c57507f80000000000000000000000000000000000000000000000000000000000000007fff000000000000000000000000000000000000000000000000000000000000008216105b15614263576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506001955093506000925061458f915050565b60bf81116143d457600061428b60b78361522c565b9050808760000151116142ca576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff0000000000000000000000000000000000000000000000000000000000000016600081900361432c576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614374576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61437e8184614fed565b8951116143b7576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6143c2836001614fed565b975095506000945061458f9350505050565b60f781116144395760006143e960c08361522c565b905080876000015111614428576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60019550935084925061458f915050565b600061444660f78361522c565b905080876000015111614485576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff000000000000000000000000000000000000000000000000000000000000001660008190036144e7576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c6037811161452f576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6145398184614fed565b895111614572576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61457d836001614fed565b975095506001945061458f9350505050565b9193909250565b60608167ffffffffffffffff8111156145b1576145b1614a8b565b6040519080825280601f01601f1916602001820160405280156145db576020820181803683370190505b50905081156129685760006145f08486614fed565b90506020820160005b848110156146115782810151828201526020016145f9565b84811115614620576000858301525b5050509392505050565b6060610ff2826020015160008460000151614596565b60608182601f0110156146af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f770000000000000000000000000000000000006044820152606401610add565b82828401101561471b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f770000000000000000000000000000000000006044820152606401610add565b81830184511015614788576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f736c6963655f6f75744f66426f756e64730000000000000000000000000000006044820152606401610add565b6060821580156147a7576040519150600082526020820160405261480f565b6040519150601f8416801560200281840101858101878315602002848b0101015b818310156147e05780518352602092830192016147c8565b5050858452601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016604052505b50949350505050565b60408051808201909152600080825260208201528151600003614867576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b60606000806000614895856140f8565b9194509250905060018160018111156148b0576148b061573b565b146148e7576040517f4b9c6abe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84516148f38385614fed565b1461492a576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516020808252610420820190925290816020015b60408051808201909152600080825260208201528152602001906001900390816149415790505093506000835b8651811015614a2f576000806149b46040518060400160405280858c60000151614998919061522c565b8152602001858c602001516149ad9190614fed565b90526140f8565b5091509150604051806040016040528083836149d09190614fed565b8152602001848b602001516149e59190614fed565b8152508885815181106149fa576149fa6156ab565b6020908102919091010152614a10600185614fed565b9350614a1c8183614fed565b614a269084614fed565b9250505061496e565b50845250919392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114614a5d57600080fd5b50565b803567ffffffffffffffff81168114614a7857600080fd5b919050565b8015158114614a5d57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614b0157614b01614a8b565b604052919050565b600082601f830112614b1a57600080fd5b813567ffffffffffffffff811115614b3457614b34614a8b565b614b6560207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601614aba565b818152846020838601011115614b7a57600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060008060c08789031215614bb057600080fd5b8635614bbb81614a3b565b95506020870135945060408701359350614bd760608801614a60565b92506080870135614be781614a7d565b915060a087013567ffffffffffffffff811115614c0357600080fd5b614c0f89828a01614b09565b9150509295509295509295565b600060c08284031215614c2e57600080fd5b60405160c0810167ffffffffffffffff8282108183111715614c5257614c52614a8b565b816040528293508435835260208501359150614c6d82614a3b565b81602084015260408501359150614c8382614a3b565b816040840152606085013560608401526080850135608084015260a0850135915080821115614cb157600080fd5b50614cbe85828601614b09565b60a0830152505092915050565b600080600080600085870360e0811215614ce457600080fd5b863567ffffffffffffffff80821115614cfc57600080fd5b614d088a838b01614c1c565b97506020890135965060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc084011215614d4157600080fd5b60408901955060c0890135925080831115614d5b57600080fd5b828901925089601f840112614d6f57600080fd5b8235915080821115614d8057600080fd5b508860208260051b8401011115614d9657600080fd5b959894975092955050506020019190565b60005b83811015614dc2578181015183820152602001614daa565b83811115611ef75750506000910152565b60008151808452614deb816020860160208601614da7565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006129686020830184614dd3565b600060208284031215614e4257600080fd5b5035919050565b60ff81168114614a5d57600080fd5b60008060008060808587031215614e6e57600080fd5b8435614e7981614a3b565b93506020850135614e8981614e49565b93969395505050506040820135916060013590565b600060208284031215614eb057600080fd5b813567ffffffffffffffff811115614ec757600080fd5b614ed384828501614c1c565b949350505050565b600060208284031215614eed57600080fd5b61296882614a60565b600080600060608486031215614f0b57600080fd5b8335614f1681614a3b565b92506020840135614f2681614a3b565b91506040840135614f3681614a3b565b809150509250925092565b600080600080600060a08688031215614f5957600080fd5b8535614f6481614a3b565b945060208601359350614f7960408701614a60565b92506060860135614f8981614a7d565b9150608086013567ffffffffffffffff811115614fa557600080fd5b614fb188828901614b09565b9150509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000821982111561500057615000614fbe565b500190565b60006020828403121561501757600080fd5b5051919050565b60006020828403121561503057600080fd5b815161296881614a3b565b80516fffffffffffffffffffffffffffffffff81168114614a7857600080fd5b60006060828403121561506d57600080fd5b6040516060810181811067ffffffffffffffff8211171561509057615090614a8b565b604052825181526150a36020840161503b565b60208201526150b46040840161503b565b60408201529392505050565b6000608082840312156150d257600080fd5b6040516080810181811067ffffffffffffffff821117156150f5576150f5614a8b565b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b600067ffffffffffffffff8084111561514157615141614a8b565b8360051b6020615152818301614aba565b86815291850191818101903684111561516a57600080fd5b865b8481101561519e578035868111156151845760008081fd5b61519036828b01614b09565b84525091830191830161516c565b50979650505050505050565b6000602082840312156151bc57600080fd5b815161296881614a7d565b8581528460208201527fffffffffffffffff0000000000000000000000000000000000000000000000008460c01b16604082015282151560f81b60488201526000825161521b816049850160208701614da7565b919091016049019695505050505050565b60008282101561523e5761523e614fbe565b500390565b600067ffffffffffffffff8083168185168183048111821515161561526a5761526a614fbe565b02949350505050565b600067ffffffffffffffff80831681851680830382111561529657615296614fbe565b01949350505050565b600080604083850312156152b257600080fd5b82516152bd81614a3b565b60208401519092506152ce81614e49565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082615317576153176152d9565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f80000000000000000000000000000000000000000000000000000000000000008314161561536b5761536b614fbe565b500590565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156153aa576153aa614fbe565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156153de576153de614fbe565b50500390565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60008413600084138583048511828216161561542557615425614fbe565b7f8000000000000000000000000000000000000000000000000000000000000000600087128682058812818416161561546057615460614fbe565b6000871292508782058712848416161561547c5761547c614fbe565b8785058712818416161561549257615492614fbe565b505050929093029392505050565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038413811516156154da576154da614fbe565b827f800000000000000000000000000000000000000000000000000000000000000003841281161561550e5761550e614fbe565b50500190565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561554c5761554c614fbe565b500290565b600082615560576155606152d9565b500490565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a08301526155b060c0830184614dd3565b98975050505050505050565b805163ffffffff81168114614a7857600080fd5b600060c082840312156155e257600080fd5b60405160c0810181811067ffffffffffffffff8211171561560557615605614a8b565b604052615611836155bc565b8152602083015161562181614e49565b6020820152604083015161563481614e49565b6040820152615645606084016155bc565b6060820152615656608084016155bc565b608082015261566760a0840161503b565b60a08201529392505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036156a4576156a4614fbe565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060ff8316806156ed576156ed6152d9565b8060ff84160691505092915050565b600060ff821660ff84168082101561571657615716614fbe565b90039392505050565b60008251615731818460208701614da7565b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c634300080f000a"; + hex"6080604052600436106101635760003560e01c80638c3152e9116100c0578063b69ef8a811610074578063cff0ab9611610059578063cff0ab9614610444578063e965084c146104e5578063e9e05c421461057157600080fd5b8063b69ef8a814610401578063cf756fdf1461042457600080fd5b80639bf62d82116100a55780639bf62d821461036b578063a14238e714610398578063a35d99df146103c857600080fd5b80638c3152e91461031e5780639b5f694a1461033e57600080fd5b806354fd4d50116101175780636dbffb78116100fc5780636dbffb78146102de57806371cfaa3f146102fe5780638b4c40b01461018857600080fd5b806354fd4d501461026d5780635c975abb146102b957600080fd5b806335e80ab31161014857806335e80ab314610206578063452a9320146102385780634870496f1461024d57600080fd5b8063149f2f221461018f57806333d7e2bd146101af57600080fd5b3661018a576101883334620186a060006040518060200160405280600081525061057f565b005b600080fd5b34801561019b57600080fd5b506101886101aa366004614ba3565b610624565b3480156101bb57600080fd5b506037546101dc9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561021257600080fd5b506035546101dc90610100900473ffffffffffffffffffffffffffffffffffffffff1681565b34801561024457600080fd5b506101dc610865565b34801561025957600080fd5b50610188610268366004614cd7565b6108fd565b34801561027957600080fd5b50604080518082018252600c81527f322e382e312d626574612e310000000000000000000000000000000000000000602082015290516101fd9190614e29565b3480156102c557600080fd5b506102ce610eaa565b60405190151581526020016101fd565b3480156102ea57600080fd5b506102ce6102f9366004614e3c565b610f3d565b34801561030a57600080fd5b50610188610319366004614e64565b610ff8565b34801561032a57600080fd5b50610188610339366004614eaa565b6111ba565b34801561034a57600080fd5b506036546101dc9073ffffffffffffffffffffffffffffffffffffffff1681565b34801561037757600080fd5b506032546101dc9073ffffffffffffffffffffffffffffffffffffffff1681565b3480156103a457600080fd5b506102ce6103b3366004614e3c565b60336020526000908152604090205460ff1681565b3480156103d457600080fd5b506103e86103e3366004614ee7565b611c3c565b60405167ffffffffffffffff90911681526020016101fd565b34801561040d57600080fd5b50610416611c55565b6040519081526020016101fd565b34801561043057600080fd5b5061018861043f366004614f02565b611caf565b34801561045057600080fd5b506001546104ac906fffffffffffffffffffffffffffffffff81169067ffffffffffffffff7001000000000000000000000000000000008204811691780100000000000000000000000000000000000000000000000090041683565b604080516fffffffffffffffffffffffffffffffff909416845267ffffffffffffffff92831660208501529116908201526060016101fd565b3480156104f157600080fd5b50610543610500366004614e3c565b603460205260009081526040902080546001909101546fffffffffffffffffffffffffffffffff8082169170010000000000000000000000000000000090041683565b604080519384526fffffffffffffffffffffffffffffffff92831660208501529116908201526060016101fd565b61018861057f366004614f53565b8260005a9050600061058f611f1f565b50905073ffffffffffffffffffffffffffffffffffffffff811673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee148015906105cb57503415155b15610602576040517ff2365b5b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610610883489898989611fbc565b5061061b8282612168565b50505050505050565b8260005a90506000610634611f1f565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8216016106a6576040517f0eaf3c0f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b87603d60008282546106b89190614fff565b90915550506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa15801561072a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074e9190615017565b905061077273ffffffffffffffffffffffffffffffffffffffff831633308c612435565b61077c8982614fff565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8416906370a0823190602401602060405180830381865afa1580156107e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061080a9190615017565b14610841576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61084f8a8a8a8a8a8a611fbc565b505061085b8282612168565b5050505050505050565b6000603560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663452a93206040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f89190615030565b905090565b610905610eaa565b1561093c576040517ff480973e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff16856040015173ffffffffffffffffffffffffffffffffffffffff16036109a5576040517f13496fda00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6036546040517fa25ae5570000000000000000000000000000000000000000000000000000000081526004810186905260009173ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa158015610a15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a39919061506d565b519050610a53610a4e368690038601866150d2565b612517565b8114610ae6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4f7074696d69736d506f7274616c3a20696e76616c6964206f7574707574207260448201527f6f6f742070726f6f66000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b6000610af187612573565b6000818152603460209081526040918290208251606081018452815481526001909101546fffffffffffffffffffffffffffffffff8082169383018490527001000000000000000000000000000000009091041692810192909252919250901580610c075750805160365460408084015190517fa25ae5570000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff9091169063a25ae55790602401606060405180830381865afa158015610bdf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c03919061506d565b5114155b610c93576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173682060448201527f68617320616c7265616479206265656e2070726f76656e0000000000000000006064820152608401610add565b60408051602081018490526000918101829052606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201209083018190529250610d5c9101604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828201909152600182527f0100000000000000000000000000000000000000000000000000000000000000602083015290610d52888a615138565b8a604001356125a3565b610de8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4f7074696d69736d506f7274616c3a20696e76616c696420776974686472617760448201527f616c20696e636c7573696f6e2070726f6f6600000000000000000000000000006064820152608401610add565b604080516060810182528581526fffffffffffffffffffffffffffffffff42811660208084019182528c831684860190815260008981526034835286812095518655925190518416700100000000000000000000000000000000029316929092176001909301929092558b830151908c0151925173ffffffffffffffffffffffffffffffffffffffff918216939091169186917f67a6208cfcc0801d50f6cbe764733f4fddf66ac0b04442061a8a8c0cb6b63f629190a4505050505050505050565b6000603560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f19573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f891906151bc565b6036546040517fa25ae55700000000000000000000000000000000000000000000000000000000815260048101839052600091610ff29173ffffffffffffffffffffffffffffffffffffffff9091169063a25ae55790602401606060405180830381865afa158015610fb3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd7919061506d565b602001516fffffffffffffffffffffffffffffffff166125c7565b92915050565b60375473ffffffffffffffffffffffffffffffffffffffff163314611049576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61105562030d4061266d565b60405173ffffffffffffffffffffffffffffffffffffffff8516602482015260ff8416604482015260648101839052608481018290526000907342000000000000000000000000000000000000159073deaddeaddeaddeaddeaddeaddeaddeaddead0001907fb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32908490819062030d4090829060a401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152602080830180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f71cfaa3f000000000000000000000000000000000000000000000000000000001790529051611172969594939291016151d9565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290526111aa91614e29565b60405180910390a450505050565b565b6111c2610eaa565b156111f9576040517ff480973e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60325473ffffffffffffffffffffffffffffffffffffffff1661dead1461124c576040517f9396d15600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061125782612573565b60008181526034602090815260408083208151606081018352815481526001909101546fffffffffffffffffffffffffffffffff80821694830185905270010000000000000000000000000000000090910416918101919091529293509003611342576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173206e60448201527f6f74206265656e2070726f76656e2079657400000000000000000000000000006064820152608401610add565b603660009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663887862726040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113af573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113d39190615017565b81602001516fffffffffffffffffffffffffffffffff16101561149e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604b60248201527f4f7074696d69736d506f7274616c3a207769746864726177616c2074696d657360448201527f74616d70206c657373207468616e204c32204f7261636c65207374617274696e60648201527f672074696d657374616d70000000000000000000000000000000000000000000608482015260a401610add565b6114bd81602001516fffffffffffffffffffffffffffffffff166125c7565b61156f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604560248201527f4f7074696d69736d506f7274616c3a2070726f76656e2077697468647261776160448201527f6c2066696e616c697a6174696f6e20706572696f6420686173206e6f7420656c60648201527f6170736564000000000000000000000000000000000000000000000000000000608482015260a401610add565b60365460408281015190517fa25ae5570000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff909116600482015260009173ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa1580156115f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061161a919061506d565b82518151919250146116d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604960248201527f4f7074696d69736d506f7274616c3a206f757470757420726f6f742070726f7660448201527f656e206973206e6f74207468652073616d652061732063757272656e74206f7560648201527f7470757420726f6f740000000000000000000000000000000000000000000000608482015260a401610add565b6116f381602001516fffffffffffffffffffffffffffffffff166125c7565b6117a5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4f7074696d69736d506f7274616c3a206f75747075742070726f706f73616c2060448201527f66696e616c697a6174696f6e20706572696f6420686173206e6f7420656c617060648201527f7365640000000000000000000000000000000000000000000000000000000000608482015260a401610add565b60008381526033602052604090205460ff1615611844576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173206160448201527f6c7265616479206265656e2066696e616c697a656400000000000000000000006064820152608401610add565b6000838152603360209081526040822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558501516032805473ffffffffffffffffffffffffffffffffffffffff9092167fffffffffffffffffffffffff0000000000000000000000000000000000000000909216919091179055806118cf611f1f565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8216016119325761192b8660400151876080015188606001518960a001516126cf565b9150611b85565b8073ffffffffffffffffffffffffffffffffffffffff16866040015173ffffffffffffffffffffffffffffffffffffffff160361199b576040517f13496fda00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606086015115611b5c578560600151603d60008282546119bb919061523e565b90915550506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa158015611a2d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a519190615017565b9050611a86876040015188606001518473ffffffffffffffffffffffffffffffffffffffff1661272d9092919063ffffffff16565b6060870151611a95908261523e565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8416906370a0823190602401602060405180830381865afa158015611aff573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b239190615017565b14611b5a576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b60a08601515115611b805761192b8660400151876080015160008960a001516126cf565b600191505b603280547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead17905560405185907fdb5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1b90611be790851515815260200190565b60405180910390a281158015611bfd5750326001145b15611c34576040517feeae4ed300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050565b6000611c49826010615255565b610ff290615208615285565b600080611c60611f1f565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff821601611ca7574791505090565b5050603d5490565b600054610100900460ff1615808015611ccf5750600054600160ff909116105b80611ce95750303b158015611ce9575060005460ff166001145b611d75576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610add565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015611dd357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b603680547fffffffffffffffffffffffff000000000000000000000000000000000000000090811673ffffffffffffffffffffffffffffffffffffffff8881169190911790925560378054909116868316179055603580547fffffffffffffffffffffff0000000000000000000000000000000000000000ff166101008684160217905560325416611e8c57603280547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790555b603d829055611e99612788565b8015611efc57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b603754604080517f4397dfef0000000000000000000000000000000000000000000000000000000081528151600093849373ffffffffffffffffffffffffffffffffffffffff90911692634397dfef92600480830193928290030181865afa158015611f8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fb391906152b1565b90939092509050565b818015611fde575073ffffffffffffffffffffffffffffffffffffffff861615155b15612015576040517f13496fda00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61201f8151611c3c565b67ffffffffffffffff168367ffffffffffffffff16101561206c576040517f4929b80800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6201d4c0815111156120aa576040517f73052b0f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b333281146120cb575033731111000000000000000000000000000000001111015b600086868686866040516020016120e69594939291906151d9565b604051602081830303815290604052905060008873ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32846040516121569190614e29565b60405180910390a45050505050505050565b60015460009061219e907801000000000000000000000000000000000000000000000000900467ffffffffffffffff164361523e565b905060006121aa61289b565b90506000816020015160ff16826000015163ffffffff166121cb919061531a565b9050821561230257600154600090612202908390700100000000000000000000000000000000900467ffffffffffffffff16615382565b90506000836040015160ff168361221991906153f6565b6001546122399084906fffffffffffffffffffffffffffffffff166153f6565b612243919061531a565b6001549091506000906122949061226d9084906fffffffffffffffffffffffffffffffff166154b2565b866060015163ffffffff168760a001516fffffffffffffffffffffffffffffffff1661295c565b905060018611156122c3576122c061226d82876040015160ff1660018a6122bb919061523e565b61297b565b90505b6fffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000067ffffffffffffffff4316021760015550505b60018054869190601090612335908490700100000000000000000000000000000000900467ffffffffffffffff16615285565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550816000015163ffffffff16600160000160109054906101000a900467ffffffffffffffff1667ffffffffffffffff1613156123c2576040517f77ebef4d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001546000906123ee906fffffffffffffffffffffffffffffffff1667ffffffffffffffff8816615526565b9050600061240048633b9aca006129d0565b61240a9083615563565b905060005a612419908861523e565b90508082111561085b5761085b612430828461523e565b6129e7565b60405173ffffffffffffffffffffffffffffffffffffffff808516602483015283166044820152606481018290526125119085907f23b872dd00000000000000000000000000000000000000000000000000000000906084015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612a10565b50505050565b60008160000151826020015183604001518460600151604051602001612556949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b80516020808301516040808501516060860151608087015160a08801519351600097612556979096959101615577565b6000806125af86612b1c565b90506125bd81868686612b4e565b9695505050505050565b603654604080517ff4daa291000000000000000000000000000000000000000000000000000000008152905160009273ffffffffffffffffffffffffffffffffffffffff169163f4daa2919160048083019260209291908290030181865afa158015612637573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061265b9190615017565b6126659083614fff565b421192915050565b6001805463ffffffff831691906010906126a6908490700100000000000000000000000000000000900467ffffffffffffffff16615285565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555050565b60008060006126df866000612b7e565b905080612715576308c379a06000526020805278185361666543616c6c3a204e6f7420656e6f756768206761736058526064601cfd5b600080855160208701888b5af1979650505050505050565b60405173ffffffffffffffffffffffffffffffffffffffff83166024820152604481018290526127839084907fa9059cbb000000000000000000000000000000000000000000000000000000009060640161248f565b505050565b600054610100900460ff1661281f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610add565b6001547801000000000000000000000000000000000000000000000000900467ffffffffffffffff166000036111b85760408051606081018252633b9aca00808252600060208301524367ffffffffffffffff169190920181905278010000000000000000000000000000000000000000000000000217600155565b6040805160c08082018352600080835260208301819052828401819052606083018190526080830181905260a083015260375483517fcc731b020000000000000000000000000000000000000000000000000000000081529351929373ffffffffffffffffffffffffffffffffffffffff9091169263cc731b02926004808401939192918290030181865afa158015612938573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108f891906155e2565b600061297161296b8585612b9c565b83612bac565b90505b9392505050565b6000670de0b6b3a76400006129bc612993858361531a565b6129a590670de0b6b3a7640000615382565b6129b785670de0b6b3a76400006153f6565b612bbb565b6129c690866153f6565b612971919061531a565b6000818310156129e05781612974565b5090919050565b6000805a90505b825a6129fa908361523e565b101561278357612a0982615685565b91506129ee565b6000612a72826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16612bec9092919063ffffffff16565b8051909150156127835780806020019051810190612a9091906151bc565b612783576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610add565b60608180519060200120604051602001612b3891815260200190565b6040516020818303038152906040529050919050565b6000612b7584612b5f878686612bfb565b8051602091820120825192909101919091201490565b95945050505050565b600080603f83619c4001026040850201603f5a021015949350505050565b6000818312156129e05781612974565b60008183126129e05781612974565b6000612974670de0b6b3a764000083612bd386613679565b612bdd91906153f6565b612be7919061531a565b6138bd565b60606129718484600085613afc565b60606000845111612c68576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4d65726b6c65547269653a20656d707479206b657900000000000000000000006044820152606401610add565b6000612c7384613c92565b90506000612c8086613d7e565b9050600084604051602001612c9791815260200190565b60405160208183030381529060405290506000805b84518110156135f0576000858281518110612cc957612cc96156bd565b602002602001015190508451831115612d64576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4d65726b6c65547269653a206b657920696e646578206578636565647320746f60448201527f74616c206b6579206c656e6774680000000000000000000000000000000000006064820152608401610add565b82600003612e1d5780518051602091820120604051612db292612d8c92910190815260200190565b604051602081830303815290604052858051602091820120825192909101919091201490565b612e18576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4d65726b6c65547269653a20696e76616c696420726f6f7420686173680000006044820152606401610add565b612f74565b805151602011612ed35780518051602091820120604051612e4792612d8c92910190815260200190565b612e18576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d65726b6c65547269653a20696e76616c6964206c6172676520696e7465726e60448201527f616c2068617368000000000000000000000000000000000000000000000000006064820152608401610add565b805184516020808701919091208251919092012014612f74576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4d65726b6c65547269653a20696e76616c696420696e7465726e616c206e6f6460448201527f65206861736800000000000000000000000000000000000000000000000000006064820152608401610add565b612f8060106001614fff565b8160200151510361315c57845183036130f457612fba8160200151601081518110612fad57612fad6156bd565b6020026020010151613de1565b9650600087511161304d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286272616e63682900000000006064820152608401610add565b6001865161305b919061523e565b82146130e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286272616e6368290000000000006064820152608401610add565b505050505050612974565b6000858481518110613108576131086156bd565b602001015160f81c60f81b60f81c9050600082602001518260ff1681518110613133576131336156bd565b6020026020010151905061314681613e95565b9550613153600186614fff565b945050506135dd565b60028160200151510361355557600061317482613eba565b905060008160008151811061318b5761318b6156bd565b016020015160f81c905060006131a26002836156ec565b6131ad90600261570e565b905060006131be848360ff16613ede565b905060006131cc8a89613ede565b905060006131da8383613f14565b90508083511461326c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4d65726b6c65547269653a20706174682072656d61696e646572206d7573742060448201527f736861726520616c6c206e6962626c65732077697468206b65790000000000006064820152608401610add565b60ff851660021480613281575060ff85166003145b156134705780825114613316576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603d60248201527f4d65726b6c65547269653a206b65792072656d61696e646572206d757374206260448201527f65206964656e746963616c20746f20706174682072656d61696e6465720000006064820152608401610add565b6133308760200151600181518110612fad57612fad6156bd565b9c5060008d51116133c3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286c65616629000000000000006064820152608401610add565b60018c516133d1919061523e565b881461345f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286c6561662900000000000000006064820152608401610add565b505050505050505050505050612974565b60ff85161580613483575060ff85166001145b156134c2576134af87602001516001815181106134a2576134a26156bd565b6020026020010151613e95565b99506134bb818a614fff565b985061354a565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4d65726b6c65547269653a2072656365697665642061206e6f6465207769746860448201527f20616e20756e6b6e6f776e2070726566697800000000000000000000000000006064820152608401610add565b5050505050506135dd565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f4d65726b6c65547269653a20726563656976656420616e20756e70617273656160448201527f626c65206e6f64650000000000000000000000000000000000000000000000006064820152608401610add565b50806135e881615685565b915050612cac565b506040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4d65726b6c65547269653a2072616e206f7574206f662070726f6f6620656c6560448201527f6d656e74730000000000000000000000000000000000000000000000000000006064820152608401610add565b60008082136136e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600960248201527f554e444546494e454400000000000000000000000000000000000000000000006044820152606401610add565b600060606136f184613fc8565b03609f8181039490941b90931c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506027d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b393909302929092017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d92915050565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdb731c958f34d94c182136138ee57506000919050565b680755bf798b4a1bf1e58212613960576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f4558505f4f564552464c4f5700000000000000000000000000000000000000006044820152606401610add565b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b606082471015613b8e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610add565b73ffffffffffffffffffffffffffffffffffffffff85163b613c0c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610add565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051613c359190615731565b60006040518083038185875af1925050503d8060008114613c72576040519150601f19603f3d011682016040523d82523d6000602084013e613c77565b606091505b5091509150613c8782828661409e565b979650505050505050565b80516060908067ffffffffffffffff811115613cb057613cb0614a97565b604051908082528060200260200182016040528015613cf557816020015b6040805180820190915260608082526020820152815260200190600190039081613cce5790505b50915060005b81811015613d77576040518060400160405280858381518110613d2057613d206156bd565b60200260200101518152602001613d4f868481518110613d4257613d426156bd565b60200260200101516140f1565b815250838281518110613d6457613d646156bd565b6020908102919091010152600101613cfb565b5050919050565b606080604051905082518060011b603f8101601f1916830160405280835250602084016020830160005b83811015613dd6578060011b82018184015160001a8060041c8253600f811660018301535050600101613da8565b509295945050505050565b60606000806000613df185614104565b919450925090506000816001811115613e0c57613e0c61574d565b14613e43576040517f1ff9b2e400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613e4d8284614fff565b855114613e86576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612b75856020015184846145a2565b60606020826000015110613eb157613eac82613de1565b610ff2565b610ff282614636565b6060610ff2613ed98360200151600081518110612fad57612fad6156bd565b613d7e565b606082518210613efd5750604080516020810190915260008152610ff2565b6129748383848651613f0f919061523e565b61464c565b6000808251845110613f27578251613f2a565b83515b90505b8082108015613fb15750828281518110613f4957613f496156bd565b602001015160f81c60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916848381518110613f8857613f886156bd565b01602001517fff0000000000000000000000000000000000000000000000000000000000000016145b15613fc157816001019150613f2d565b5092915050565b6000808211614033576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600960248201527f554e444546494e454400000000000000000000000000000000000000000000006044820152606401610add565b5060016fffffffffffffffffffffffffffffffff821160071b82811c67ffffffffffffffff1060061b1782811c63ffffffff1060051b1782811c61ffff1060041b1782811c60ff10600390811b90911783811c600f1060021b1783811c909110821b1791821c111790565b606083156140ad575081612974565b8251156140bd5782518084602001fd5b816040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9190614e29565b6060610ff26140ff83614824565b614891565b60008060008360000151600003614147576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020840151805160001a607f811161416c57600060016000945094509450505061459b565b60b7811161428257600061418160808361523e565b9050808760000151116141c0576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001838101517fff0000000000000000000000000000000000000000000000000000000000000016908214801561423857507f80000000000000000000000000000000000000000000000000000000000000007fff000000000000000000000000000000000000000000000000000000000000008216105b1561426f576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506001955093506000925061459b915050565b60bf81116143e057600061429760b78361523e565b9050808760000151116142d6576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614338576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614380576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61438a8184614fff565b8951116143c3576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6143ce836001614fff565b975095506000945061459b9350505050565b60f781116144455760006143f560c08361523e565b905080876000015111614434576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60019550935084925061459b915050565b600061445260f78361523e565b905080876000015111614491576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff000000000000000000000000000000000000000000000000000000000000001660008190036144f3576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c6037811161453b576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6145458184614fff565b89511161457e576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614589836001614fff565b975095506001945061459b9350505050565b9193909250565b60608167ffffffffffffffff8111156145bd576145bd614a97565b6040519080825280601f01601f1916602001820160405280156145e7576020820181803683370190505b50905081156129745760006145fc8486614fff565b90506020820160005b8481101561461d578281015182820152602001614605565b8481111561462c576000858301525b5050509392505050565b6060610ff28260200151600084600001516145a2565b60608182601f0110156146bb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f770000000000000000000000000000000000006044820152606401610add565b828284011015614727576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f770000000000000000000000000000000000006044820152606401610add565b81830184511015614794576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f736c6963655f6f75744f66426f756e64730000000000000000000000000000006044820152606401610add565b6060821580156147b3576040519150600082526020820160405261481b565b6040519150601f8416801560200281840101858101878315602002848b0101015b818310156147ec5780518352602092830192016147d4565b5050858452601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016604052505b50949350505050565b60408051808201909152600080825260208201528151600003614873576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b606060008060006148a185614104565b9194509250905060018160018111156148bc576148bc61574d565b146148f3576040517f4b9c6abe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84516148ff8385614fff565b14614936576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516020808252610420820190925290816020015b604080518082019091526000808252602082015281526020019060019003908161494d5790505093506000835b8651811015614a3b576000806149c06040518060400160405280858c600001516149a4919061523e565b8152602001858c602001516149b99190614fff565b9052614104565b5091509150604051806040016040528083836149dc9190614fff565b8152602001848b602001516149f19190614fff565b815250888581518110614a0657614a066156bd565b6020908102919091010152614a1c600185614fff565b9350614a288183614fff565b614a329084614fff565b9250505061497a565b50845250919392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114614a6957600080fd5b50565b803567ffffffffffffffff81168114614a8457600080fd5b919050565b8015158114614a6957600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614b0d57614b0d614a97565b604052919050565b600082601f830112614b2657600080fd5b813567ffffffffffffffff811115614b4057614b40614a97565b614b7160207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601614ac6565b818152846020838601011115614b8657600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060008060c08789031215614bbc57600080fd5b8635614bc781614a47565b95506020870135945060408701359350614be360608801614a6c565b92506080870135614bf381614a89565b915060a087013567ffffffffffffffff811115614c0f57600080fd5b614c1b89828a01614b15565b9150509295509295509295565b600060c08284031215614c3a57600080fd5b60405160c0810167ffffffffffffffff8282108183111715614c5e57614c5e614a97565b816040528293508435835260208501359150614c7982614a47565b81602084015260408501359150614c8f82614a47565b816040840152606085013560608401526080850135608084015260a0850135915080821115614cbd57600080fd5b50614cca85828601614b15565b60a0830152505092915050565b600080600080600085870360e0811215614cf057600080fd5b863567ffffffffffffffff80821115614d0857600080fd5b614d148a838b01614c28565b97506020890135965060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc084011215614d4d57600080fd5b60408901955060c0890135925080831115614d6757600080fd5b828901925089601f840112614d7b57600080fd5b8235915080821115614d8c57600080fd5b508860208260051b8401011115614da257600080fd5b959894975092955050506020019190565b60005b83811015614dce578181015183820152602001614db6565b838111156125115750506000910152565b60008151808452614df7816020860160208601614db3565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006129746020830184614ddf565b600060208284031215614e4e57600080fd5b5035919050565b60ff81168114614a6957600080fd5b60008060008060808587031215614e7a57600080fd5b8435614e8581614a47565b93506020850135614e9581614e55565b93969395505050506040820135916060013590565b600060208284031215614ebc57600080fd5b813567ffffffffffffffff811115614ed357600080fd5b614edf84828501614c28565b949350505050565b600060208284031215614ef957600080fd5b61297482614a6c565b60008060008060808587031215614f1857600080fd5b8435614f2381614a47565b93506020850135614f3381614a47565b92506040850135614f4381614a47565b9396929550929360600135925050565b600080600080600060a08688031215614f6b57600080fd5b8535614f7681614a47565b945060208601359350614f8b60408701614a6c565b92506060860135614f9b81614a89565b9150608086013567ffffffffffffffff811115614fb757600080fd5b614fc388828901614b15565b9150509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000821982111561501257615012614fd0565b500190565b60006020828403121561502957600080fd5b5051919050565b60006020828403121561504257600080fd5b815161297481614a47565b80516fffffffffffffffffffffffffffffffff81168114614a8457600080fd5b60006060828403121561507f57600080fd5b6040516060810181811067ffffffffffffffff821117156150a2576150a2614a97565b604052825181526150b56020840161504d565b60208201526150c66040840161504d565b60408201529392505050565b6000608082840312156150e457600080fd5b6040516080810181811067ffffffffffffffff8211171561510757615107614a97565b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b600067ffffffffffffffff8084111561515357615153614a97565b8360051b6020615164818301614ac6565b86815291850191818101903684111561517c57600080fd5b865b848110156151b0578035868111156151965760008081fd5b6151a236828b01614b15565b84525091830191830161517e565b50979650505050505050565b6000602082840312156151ce57600080fd5b815161297481614a89565b8581528460208201527fffffffffffffffff0000000000000000000000000000000000000000000000008460c01b16604082015282151560f81b60488201526000825161522d816049850160208701614db3565b919091016049019695505050505050565b60008282101561525057615250614fd0565b500390565b600067ffffffffffffffff8083168185168183048111821515161561527c5761527c614fd0565b02949350505050565b600067ffffffffffffffff8083168185168083038211156152a8576152a8614fd0565b01949350505050565b600080604083850312156152c457600080fd5b82516152cf81614a47565b60208401519092506152e081614e55565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082615329576153296152eb565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f80000000000000000000000000000000000000000000000000000000000000008314161561537d5761537d614fd0565b500590565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156153bc576153bc614fd0565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156153f0576153f0614fd0565b50500390565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60008413600084138583048511828216161561543757615437614fd0565b7f8000000000000000000000000000000000000000000000000000000000000000600087128682058812818416161561547257615472614fd0565b6000871292508782058712848416161561548e5761548e614fd0565b878505871281841616156154a4576154a4614fd0565b505050929093029392505050565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038413811516156154ec576154ec614fd0565b827f800000000000000000000000000000000000000000000000000000000000000003841281161561552057615520614fd0565b50500190565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561555e5761555e614fd0565b500290565b600082615572576155726152eb565b500490565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a08301526155c260c0830184614ddf565b98975050505050505050565b805163ffffffff81168114614a8457600080fd5b600060c082840312156155f457600080fd5b60405160c0810181811067ffffffffffffffff8211171561561757615617614a97565b604052615623836155ce565b8152602083015161563381614e55565b6020820152604083015161564681614e55565b6040820152615657606084016155ce565b6060820152615668608084016155ce565b608082015261567960a0840161504d565b60a08201529392505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036156b6576156b6614fd0565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060ff8316806156ff576156ff6152eb565b8060ff84160691505092915050565b600060ff821660ff84168082101561572857615728614fd0565b90039392505050565b60008251615743818460208701614db3565b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c634300080f000a"; bytes internal constant l1CrossDomainMessengerCode = hex"60806040526004361061018b5760003560e01c80636425666b116100d6578063b1b1b2091161007f578063d764ad0b11610059578063d764ad0b1461049b578063db505d80146104ae578063ecc70428146104db57600080fd5b8063b1b1b2091461042b578063b28ade251461045b578063c0c53b8b1461047b57600080fd5b80638cbeeef2116100b05780638cbeeef2146102d05780639fce812c146103d0578063a4e7f8bd146103fb57600080fd5b80636425666b146103775780636e296e45146103a457806383a74074146103b957600080fd5b80633dbb202b1161013857806354fd4d501161011257806354fd4d50146102e65780635644cfdf1461033c5780635c975abb1461035257600080fd5b80633dbb202b146102935780633f827a5a146102a85780634c1d6a69146102d057600080fd5b80632828d7e8116101695780632828d7e81461022457806333d7e2bd1461023957806335e80ab31461026657600080fd5b8063028f85f7146101905780630c568498146101c35780630ff754ea146101d8575b600080fd5b34801561019c57600080fd5b506101a5601081565b60405167ffffffffffffffff90911681526020015b60405180910390f35b3480156101cf57600080fd5b506101a5603f81565b3480156101e457600080fd5b5060fc5473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101ba565b34801561023057600080fd5b506101a5604081565b34801561024557600080fd5b5060fd546101ff9073ffffffffffffffffffffffffffffffffffffffff1681565b34801561027257600080fd5b5060fb546101ff9073ffffffffffffffffffffffffffffffffffffffff1681565b6102a66102a1366004611bdd565b610540565b005b3480156102b457600080fd5b506102bd600181565b60405161ffff90911681526020016101ba565b3480156102dc57600080fd5b506101a5619c4081565b3480156102f257600080fd5b5061032f6040518060400160405280600581526020017f322e342e3000000000000000000000000000000000000000000000000000000081525081565b6040516101ba9190611caf565b34801561034857600080fd5b506101a561138881565b34801561035e57600080fd5b5061036761083d565b60405190151581526020016101ba565b34801561038357600080fd5b5060fc546101ff9073ffffffffffffffffffffffffffffffffffffffff1681565b3480156103b057600080fd5b506101ff6108d6565b3480156103c557600080fd5b506101a562030d4081565b3480156103dc57600080fd5b5060cf5473ffffffffffffffffffffffffffffffffffffffff166101ff565b34801561040757600080fd5b50610367610416366004611cc9565b60ce6020526000908152604090205460ff1681565b34801561043757600080fd5b50610367610446366004611cc9565b60cb6020526000908152604090205460ff1681565b34801561046757600080fd5b506101a5610476366004611ce2565b6109bd565b34801561048757600080fd5b506102a6610496366004611d36565b610a2b565b6102a66104a9366004611d81565b610ca2565b3480156104ba57600080fd5b5060cf546101ff9073ffffffffffffffffffffffffffffffffffffffff1681565b3480156104e757600080fd5b5061053260cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016101ba565b6105486115d3565b156105e05734156105e0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603d60248201527f43726f7373446f6d61696e4d657373656e6765723a2063616e6e6f742073656e60448201527f642076616c7565207769746820637573746f6d2067617320746f6b656e00000060648201526084015b60405180910390fd5b60cf546107129073ffffffffffffffffffffffffffffffffffffffff166106088585856109bd565b347fd764ad0b0000000000000000000000000000000000000000000000000000000061067460cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b338a34898c8c6040516024016106909796959493929190611e50565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152611612565b8373ffffffffffffffffffffffffffffffffffffffff167fcb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52a33858561079760cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b866040516107a9959493929190611eaf565b60405180910390a260405134815233907f8ebb2ec2465bdb2a06a66fc37a0963af8a2a6a1479d81d56fdb8cbb98096d5469060200160405180910390a2505060cd80547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808216600101167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b60fb54604080517f5c975abb000000000000000000000000000000000000000000000000000000008152905160009273ffffffffffffffffffffffffffffffffffffffff1691635c975abb9160048083019260209291908290030181865afa1580156108ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d19190611efd565b905090565b60cc5460009073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2153016109a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f43726f7373446f6d61696e4d657373656e6765723a2078446f6d61696e4d657360448201527f7361676553656e646572206973206e6f7420736574000000000000000000000060648201526084016105d7565b5060cc5473ffffffffffffffffffffffffffffffffffffffff1690565b6000611388619c4080603f6109d9604063ffffffff8816611f4e565b6109e39190611f7e565b6109ee601088611f4e565b6109fb9062030d40611fcc565b610a059190611fcc565b610a0f9190611fcc565b610a199190611fcc565b610a239190611fcc565b949350505050565b6000547501000000000000000000000000000000000000000000900460ff1615808015610a76575060005460017401000000000000000000000000000000000000000090910460ff16105b80610aa85750303b158015610aa8575060005474010000000000000000000000000000000000000000900460ff166001145b610b34576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016105d7565b600080547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790558015610bba57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1675010000000000000000000000000000000000000000001790555b60fb805473ffffffffffffffffffffffffffffffffffffffff8087167fffffffffffffffffffffffff00000000000000000000000000000000000000009283161790925560fc805486841690831617905560fd805492851692909116919091179055610c397342000000000000000000000000000000000000076116ab565b8015610c9c57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b610caa61083d565b15610d11576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f43726f7373446f6d61696e4d657373656e6765723a207061757365640000000060448201526064016105d7565b60f087901c60028110610dcc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f43726f7373446f6d61696e4d657373656e6765723a206f6e6c7920766572736960448201527f6f6e2030206f722031206d657373616765732061726520737570706f7274656460648201527f20617420746869732074696d6500000000000000000000000000000000000000608482015260a4016105d7565b8061ffff16600003610ec1576000610e1d878986868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508f92506117e7915050565b600081815260cb602052604090205490915060ff1615610ebf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f43726f7373446f6d61696e4d657373656e6765723a206c65676163792077697460448201527f6864726177616c20616c72656164792072656c6179656400000000000000000060648201526084016105d7565b505b6000610f07898989898989898080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061180692505050565b9050610f11611829565b15610f4957853414610f2557610f25611ff8565b600081815260ce602052604090205460ff1615610f4457610f44611ff8565b61109b565b3415610ffd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605060248201527f43726f7373446f6d61696e4d657373656e6765723a2076616c7565206d75737460448201527f206265207a65726f20756e6c657373206d6573736167652069732066726f6d2060648201527f612073797374656d206164647265737300000000000000000000000000000000608482015260a4016105d7565b600081815260ce602052604090205460ff1661109b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f43726f7373446f6d61696e4d657373656e6765723a206d65737361676520636160448201527f6e6e6f74206265207265706c617965640000000000000000000000000000000060648201526084016105d7565b6110a487611905565b15611157576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f43726f7373446f6d61696e4d657373656e6765723a2063616e6e6f742073656e60448201527f64206d65737361676520746f20626c6f636b65642073797374656d206164647260648201527f6573730000000000000000000000000000000000000000000000000000000000608482015260a4016105d7565b600081815260cb602052604090205460ff16156111f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f43726f7373446f6d61696e4d657373656e6765723a206d65737361676520686160448201527f7320616c7265616479206265656e2072656c617965640000000000000000000060648201526084016105d7565b61121785611208611388619c40611fcc565b67ffffffffffffffff1661194b565b158061123d575060cc5473ffffffffffffffffffffffffffffffffffffffff1661dead14155b1561135657600081815260ce602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555182917f99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f91a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff320161134f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f43726f7373446f6d61696e4d657373656e6765723a206661696c656420746f2060448201527f72656c6179206d6573736167650000000000000000000000000000000000000060648201526084016105d7565b50506115ae565b60cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8a1617905560006113e788619c405a6113aa9190612027565b8988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061196992505050565b60cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790559050801561149d57600082815260cb602052604090205460ff161561143a5761143a611ff8565b600082815260cb602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183917f4641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133c91a26115aa565b600082815260ce602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183917f99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f91a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff32016115aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f43726f7373446f6d61696e4d657373656e6765723a206661696c656420746f2060448201527f72656c6179206d6573736167650000000000000000000000000000000000000060648201526084016105d7565b5050505b50505050505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b6000806115de611981565b5073ffffffffffffffffffffffffffffffffffffffff1673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee141592915050565b60fc546040517fe9e05c4200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063e9e05c4290849061167390889083908990600090899060040161203e565b6000604051808303818588803b15801561168c57600080fd5b505af11580156116a0573d6000803e3d6000fd5b505050505050505050565b6000547501000000000000000000000000000000000000000000900460ff16611756576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016105d7565b60cc5473ffffffffffffffffffffffffffffffffffffffff166117a05760cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790555b60cf80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60006117f585858585611a1e565b805190602001209050949350505050565b6000611816878787878787611ab7565b8051906020012090509695505050505050565b60fc5460009073ffffffffffffffffffffffffffffffffffffffff16331480156108d1575060cf5460fc54604080517f9bf62d82000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff9384169390921691639bf62d82916004808201926020929091908290030181865afa1580156118c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118e99190612096565b73ffffffffffffffffffffffffffffffffffffffff1614905090565b600073ffffffffffffffffffffffffffffffffffffffff8216301480611945575060fc5473ffffffffffffffffffffffffffffffffffffffff8381169116145b92915050565b600080603f83619c4001026040850201603f5a021015949350505050565b6000806000835160208501868989f195945050505050565b60fd54604080517f4397dfef0000000000000000000000000000000000000000000000000000000081528151600093849373ffffffffffffffffffffffffffffffffffffffff90911692634397dfef92600480830193928290030181865afa1580156119f1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a1591906120b3565b90939092509050565b606084848484604051602401611a3794939291906120f3565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fcbd4ece9000000000000000000000000000000000000000000000000000000001790529050949350505050565b6060868686868686604051602401611ad49695949392919061213d565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fd764ad0b0000000000000000000000000000000000000000000000000000000017905290509695505050505050565b73ffffffffffffffffffffffffffffffffffffffff81168114611b7857600080fd5b50565b60008083601f840112611b8d57600080fd5b50813567ffffffffffffffff811115611ba557600080fd5b602083019150836020828501011115611bbd57600080fd5b9250929050565b803563ffffffff81168114611bd857600080fd5b919050565b60008060008060608587031215611bf357600080fd5b8435611bfe81611b56565b9350602085013567ffffffffffffffff811115611c1a57600080fd5b611c2687828801611b7b565b9094509250611c39905060408601611bc4565b905092959194509250565b6000815180845260005b81811015611c6a57602081850181015186830182015201611c4e565b81811115611c7c576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000611cc26020830184611c44565b9392505050565b600060208284031215611cdb57600080fd5b5035919050565b600080600060408486031215611cf757600080fd5b833567ffffffffffffffff811115611d0e57600080fd5b611d1a86828701611b7b565b9094509250611d2d905060208501611bc4565b90509250925092565b600080600060608486031215611d4b57600080fd5b8335611d5681611b56565b92506020840135611d6681611b56565b91506040840135611d7681611b56565b809150509250925092565b600080600080600080600060c0888a031215611d9c57600080fd5b873596506020880135611dae81611b56565b95506040880135611dbe81611b56565b9450606088013593506080880135925060a088013567ffffffffffffffff811115611de857600080fd5b611df48a828b01611b7b565b989b979a50959850939692959293505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b878152600073ffffffffffffffffffffffffffffffffffffffff808916602084015280881660408401525085606083015263ffffffff8516608083015260c060a0830152611ea260c083018486611e07565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff86168152608060208201526000611edf608083018688611e07565b905083604083015263ffffffff831660608301529695505050505050565b600060208284031215611f0f57600080fd5b81518015158114611cc257600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615611f7557611f75611f1f565b02949350505050565b600067ffffffffffffffff80841680611fc0577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b92169190910492915050565b600067ffffffffffffffff808316818516808303821115611fef57611fef611f1f565b01949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b60008282101561203957612039611f1f565b500390565b73ffffffffffffffffffffffffffffffffffffffff8616815284602082015267ffffffffffffffff84166040820152821515606082015260a06080820152600061208b60a0830184611c44565b979650505050505050565b6000602082840312156120a857600080fd5b8151611cc281611b56565b600080604083850312156120c657600080fd5b82516120d181611b56565b602084015190925060ff811681146120e857600080fd5b809150509250929050565b600073ffffffffffffffffffffffffffffffffffffffff80871683528086166020840152506080604083015261212c6080830185611c44565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261218860c0830184611c44565b9897505050505050505056fea164736f6c634300080f000a"; bytes internal constant l2OutputOracleCode =
diff --git OP/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryFaultProofs.sol CELO/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryFaultProofs.sol index 30d8e8be3fb4fd64cb9afee735f27adb8d37c2ed..244570d908af83b509b5cc48e3b781e54c0ff2cc 100644 --- OP/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryFaultProofs.sol +++ CELO/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryFaultProofs.sol @@ -25,7 +25,7 @@ address internal constant l1ERC721BridgeAddress = 0x5C4F5e749A61a9503c4AAE8a9393e89609a0e804; address internal constant l1ERC721BridgeProxyAddress = 0xD31598c909d9C935a9e35bA70d9a3DD47d4D5865; address internal constant l1StandardBridgeAddress = 0xb7900B27Be8f0E0fF65d1C3A4671e1220437dd2b; address internal constant l1StandardBridgeProxyAddress = 0xDeF3bca8c80064589E6787477FFa7Dd616B5574F; - address internal constant mipsAddress = 0x1C0e3B8e58dd91536Caf37a6009536255A7816a6; + address internal constant mipsAddress = 0x28bF1582225713139c0E898326Db808B6484cFd4; address internal constant optimismPortal2Address = 0xfcbb237388CaF5b08175C9927a37aB6450acd535; address internal constant optimismPortalProxyAddress = 0x978e3286EB805934215a88694d80b09aDed68D90; address internal constant preimageOracleAddress = 0x3bd7E801E51d48c5d94Ea68e8B801DFFC275De75;
diff --git OP/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryFaultProofsCode.sol CELO/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryFaultProofsCode.sol index c983a7ed52770cafd2ad26b5052aee3d0b9b36af..9b7795c24193e9fcd200f37e8c7862a50fe37408 100644 --- OP/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryFaultProofsCode.sol +++ CELO/packages/contracts-bedrock/test/kontrol/proofs/utils/DeploymentSummaryFaultProofsCode.sol @@ -55,11 +55,11 @@ hex"6080604052600436106101845760003560e01c8063715018a6116100d6578063a9059cbb1161007f578063dd62ed3e11610059578063dd62ed3e1461051c578063f2fde38b14610554578063f3fef3a31461057457610193565b8063a9059cbb146104a8578063cd47bde1146104c8578063d0e30db01461019357610193565b80638da5cb5b116100b05780638da5cb5b1461041757806395d89b4114610442578063977a5ec51461048857610193565b8063715018a61461039057806379502c55146103a55780637eee288d146103f757610193565b80632e1a7d4d1161013857806354fd4d501161011257806354fd4d50146102e75780636a42b8f81461033057806370a082311461036357610193565b80632e1a7d4d14610280578063313ce567146102a0578063485cc955146102c757610193565b80630ca35682116101695780630ca356821461022357806318160ddd1461024357806323b872dd1461026057610193565b806306fdde031461019b578063095ea7b3146101f357610193565b3661019357610191610594565b005b610191610594565b3480156101a757600080fd5b5060408051808201909152600d81527f577261707065642045746865720000000000000000000000000000000000000060208201525b6040516101ea9190611378565b60405180910390f35b3480156101ff57600080fd5b5061021361020e36600461140d565b6105ef565b60405190151581526020016101ea565b34801561022f57600080fd5b5061019161023e366004611439565b610668565b34801561024f57600080fd5b50475b6040519081526020016101ea565b34801561026c57600080fd5b5061021361027b366004611452565b610734565b34801561028c57600080fd5b5061019161029b366004611439565b61094b565b3480156102ac57600080fd5b506102b5601281565b60405160ff90911681526020016101ea565b3480156102d357600080fd5b506101916102e2366004611493565b610958565b3480156102f357600080fd5b506101dd6040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b34801561033c57600080fd5b507f0000000000000000000000000000000000000000000000000000000000093a80610252565b34801561036f57600080fd5b5061025261037e3660046114cc565b60656020526000908152604090205481565b34801561039c57600080fd5b50610191610b34565b3480156103b157600080fd5b506068546103d29073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101ea565b34801561040357600080fd5b5061019161041236600461140d565b610b48565b34801561042357600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166103d2565b34801561044e57600080fd5b5060408051808201909152600481527f574554480000000000000000000000000000000000000000000000000000000060208201526101dd565b34801561049457600080fd5b506101916104a336600461140d565b610b9c565b3480156104b457600080fd5b506102136104c336600461140d565b610c89565b3480156104d457600080fd5b506105076104e3366004611493565b60676020908152600092835260408084209091529082529020805460019091015482565b604080519283526020830191909152016101ea565b34801561052857600080fd5b50610252610537366004611493565b606660209081526000928352604080842090915290825290205481565b34801561056057600080fd5b5061019161056f3660046114cc565b610c9d565b34801561058057600080fd5b5061019161058f36600461140d565b610d51565b33600090815260656020526040812080543492906105b3908490611518565b909155505060405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b33600081815260666020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906106579086815260200190565b60405180910390a350600192915050565b60335473ffffffffffffffffffffffffffffffffffffffff1633146106ee576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f44656c61796564574554483a206e6f74206f776e65720000000000000000000060448201526064015b60405180910390fd5b60004782106106fd57476106ff565b815b604051909150339082156108fc029083906000818181858888f1935050505015801561072f573d6000803e3d6000fd5b505050565b73ffffffffffffffffffffffffffffffffffffffff831660009081526065602052604081205482111561076657600080fd5b73ffffffffffffffffffffffffffffffffffffffff841633148015906107dc575073ffffffffffffffffffffffffffffffffffffffff841660009081526066602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14155b156108645773ffffffffffffffffffffffffffffffffffffffff8416600090815260666020908152604080832033845290915290205482111561081e57600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526066602090815260408083203384529091528120805484929061085e908490611530565b90915550505b73ffffffffffffffffffffffffffffffffffffffff841660009081526065602052604081208054849290610899908490611530565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600090815260656020526040812080548492906108d3908490611518565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161093991815260200190565b60405180910390a35060019392505050565b6109553382610d51565b50565b600054610100900460ff16158080156109785750600054600160ff909116105b806109925750303b158015610992575060005460ff166001145b610a1e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016106e5565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015610a7c57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b610a8461109b565b610a8d8361113a565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8416179055801561072f57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a1505050565b610b3c6111b1565b610b46600061113a565b565b33600090815260676020908152604080832073ffffffffffffffffffffffffffffffffffffffff861684529091528120426001820155805490918391839190610b92908490611518565b9091555050505050565b60335473ffffffffffffffffffffffffffffffffffffffff163314610c1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f44656c61796564574554483a206e6f74206f776e65720000000000000000000060448201526064016106e5565b73ffffffffffffffffffffffffffffffffffffffff821660008181526066602090815260408083203380855290835292819020859055518481529192917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a35050565b6000610c96338484610734565b9392505050565b610ca56111b1565b73ffffffffffffffffffffffffffffffffffffffff8116610d48576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016106e5565b6109558161113a565b606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610dbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610de29190611547565b15610e49576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f44656c61796564574554483a20636f6e7472616374206973207061757365640060448201526064016106e5565b33600090815260676020908152604080832073ffffffffffffffffffffffffffffffffffffffff8616845290915290208054821115610f0a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f44656c61796564574554483a20696e73756666696369656e7420756e6c6f636b60448201527f6564207769746864726177616c0000000000000000000000000000000000000060648201526084016106e5565b6000816001015411610f9d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f44656c61796564574554483a207769746864726177616c206e6f7420756e6c6f60448201527f636b65640000000000000000000000000000000000000000000000000000000060648201526084016106e5565b427f0000000000000000000000000000000000000000000000000000000000093a808260010154610fce9190611518565b111561105c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f44656c61796564574554483a207769746864726177616c2064656c6179206e6f60448201527f74206d657400000000000000000000000000000000000000000000000000000060648201526084016106e5565b818160000160008282546110709190611530565b9091555061072f905082611232565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b600054610100900460ff16611132576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016106e5565b610b466112d8565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60335473ffffffffffffffffffffffffffffffffffffffff163314610b46576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106e5565b3360009081526065602052604090205481111561124e57600080fd5b336000908152606560205260408120805483929061126d908490611530565b9091555050604051339082156108fc029083906000818181858888f1935050505015801561129f573d6000803e3d6000fd5b5060405181815233907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a250565b600054610100900460ff1661136f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016106e5565b610b463361113a565b600060208083528351808285015260005b818110156113a557858101830151858201604001528201611389565b818111156113b7576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461095557600080fd5b6000806040838503121561142057600080fd5b823561142b816113eb565b946020939093013593505050565b60006020828403121561144b57600080fd5b5035919050565b60008060006060848603121561146757600080fd5b8335611472816113eb565b92506020840135611482816113eb565b929592945050506040919091013590565b600080604083850312156114a657600080fd5b82356114b1816113eb565b915060208301356114c1816113eb565b809150509250929050565b6000602082840312156114de57600080fd5b8135610c96816113eb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000821982111561152b5761152b6114e9565b500190565b600082821015611542576115426114e9565b500390565b60006020828403121561155957600080fd5b81518015158114610c9657600080fdfea164736f6c634300080f000a"; bytes internal constant preimageOracleCode = hex"6080604052600436106101cd5760003560e01c80638dc4be11116100f7578063dd24f9bf11610095578063ec5efcbc11610064578063ec5efcbc1461065f578063f3f480d91461067f578063faf37bc7146106b2578063fef2b4ed146106c557600080fd5b8063dd24f9bf1461059f578063ddcd58de146105d2578063e03110e11461060a578063e15926111461063f57600080fd5b8063b2e67ba8116100d1578063b2e67ba814610512578063b4801e611461054a578063d18534b51461056a578063da35c6641461058a57600080fd5b80638dc4be11146104835780639d53a648146104a35780639d7e8769146104f257600080fd5b806354fd4d501161016f5780637917de1d1161013e5780637917de1d146103bf5780637ac54767146103df5780638542cf50146103ff578063882856ef1461044a57600080fd5b806354fd4d50146102dd57806361238bde146103335780636551927b1461036b5780637051472e146103a357600080fd5b80632055b36b116101ab5780632055b36b146102735780633909af5c146102885780634d52b4c9146102a857806352f0f3ad146102bd57600080fd5b8063013cf08b146101d25780630359a5631461022357806304697c7814610251575b600080fd5b3480156101de57600080fd5b506101f26101ed366004612d2f565b6106f2565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152015b60405180910390f35b34801561022f57600080fd5b5061024361023e366004612d71565b610737565b60405190815260200161021a565b34801561025d57600080fd5b5061027161026c366004612de4565b61086f565b005b34801561027f57600080fd5b50610243601081565b34801561029457600080fd5b506102716102a3366004613008565b6109a5565b3480156102b457600080fd5b50610243610bfc565b3480156102c957600080fd5b506102436102d83660046130f4565b610c17565b3480156102e957600080fd5b506103266040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161021a919061315b565b34801561033f57600080fd5b5061024361034e3660046131ac565b600160209081526000928352604080842090915290825290205481565b34801561037757600080fd5b50610243610386366004612d71565b601560209081526000928352604080842090915290825290205481565b3480156103af57600080fd5b506102436703782dace9d9000081565b3480156103cb57600080fd5b506102716103da3660046131ce565b610cec565b3480156103eb57600080fd5b506102436103fa366004612d2f565b6111ef565b34801561040b57600080fd5b5061043a61041a3660046131ac565b600260209081526000928352604080842090915290825290205460ff1681565b604051901515815260200161021a565b34801561045657600080fd5b5061046a61046536600461326a565b611206565b60405167ffffffffffffffff909116815260200161021a565b34801561048f57600080fd5b5061027161049e36600461329d565b611260565b3480156104af57600080fd5b506102436104be366004612d71565b73ffffffffffffffffffffffffffffffffffffffff9091166000908152601860209081526040808320938352929052205490565b3480156104fe57600080fd5b5061027161050d3660046132e9565b61135b565b34801561051e57600080fd5b5061024361052d366004612d71565b601760209081526000928352604080842090915290825290205481565b34801561055657600080fd5b5061024361056536600461326a565b611512565b34801561057657600080fd5b50610271610585366004613008565b611544565b34801561059657600080fd5b50601354610243565b3480156105ab57600080fd5b507f0000000000000000000000000000000000000000000000000000000000002710610243565b3480156105de57600080fd5b506102436105ed366004612d71565b601660209081526000928352604080842090915290825290205481565b34801561061657600080fd5b5061062a6106253660046131ac565b611906565b6040805192835260208301919091520161021a565b34801561064b57600080fd5b5061027161065a36600461329d565b6119f7565b34801561066b57600080fd5b5061027161067a366004613375565b611aff565b34801561068b57600080fd5b507f0000000000000000000000000000000000000000000000000000000000000078610243565b6102716106c036600461340e565b611c85565b3480156106d157600080fd5b506102436106e0366004612d2f565b60006020819052908152604090205481565b6013818154811061070257600080fd5b60009182526020909120600290910201805460019091015473ffffffffffffffffffffffffffffffffffffffff909116915082565b73ffffffffffffffffffffffffffffffffffffffff82166000908152601560209081526040808320848452909152812054819061077a9060601c63ffffffff1690565b63ffffffff16905060005b6010811015610867578160011660010361080d5773ffffffffffffffffffffffffffffffffffffffff85166000908152601460209081526040808320878452909152902081601081106107da576107da61344a565b0154604080516020810192909252810184905260600160405160208183030381529060405280519060200120925061084e565b82600382601081106108215761082161344a565b01546040805160208101939093528201526060016040516020818303038152906040528051906020012092505b60019190911c908061085f816134a8565b915050610785565b505092915050565b600080600080608060146030823785878260140137601480870182207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f06000000000000000000000000000000000000000000000000000000000000001794506000908190889084018b5afa94503d60010191506008820189106108fc5763fe2549876000526004601cfd5b60c082901b81526008018481533d6000600183013e88017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8015160008481526002602090815260408083208c8452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915587845282528083209b83529a81528a82209290925593845283905296909120959095555050505050565b60006109b18a8a610737565b90506109d486868360208b01356109cf6109ca8d6134e0565b611ef0565b611f30565b80156109f257506109f283838360208801356109cf6109ca8a6134e0565b610a28576040517f09bde33900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b866040013588604051602001610a3e91906135af565b6040516020818303038152906040528051906020012014610a8b576040517f1968a90200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b836020013587602001356001610aa191906135ed565b14610ad8576040517f9a3b119900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610b2088610ae68680613605565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611f9192505050565b610b29886120ec565b836040013588604051602001610b3f91906135af565b6040516020818303038152906040528051906020012003610b8c576040517f9843145b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8a1660009081526015602090815260408083208c8452909152902080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166001179055610bf08a8a33612894565b50505050505050505050565b6001610c0a6010600261378c565b610c149190613798565b81565b6000610c23868661294d565b9050610c308360086135ed565b821180610c3d5750602083115b15610c74576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000602081815260c085901b82526008959095528251828252600286526040808320858452875280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091558484528752808320948352938652838220558181529384905292205592915050565b60608115610d0557610cfe86866129fa565b9050610d3f565b85858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509293505050505b3360009081526014602090815260408083208b845290915280822081516102008101928390529160109082845b815481526020019060010190808311610d6c57505050505090506000601560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008b81526020019081526020016000205490506000610ded8260601c63ffffffff1690565b63ffffffff169050333214610e2e576040517fba092d1600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e3e8260801c63ffffffff1690565b63ffffffff16600003610e7d576040517f87138d5c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e878260c01c90565b67ffffffffffffffff1615610ec8576040517f475a253500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b898114610f01576040517f60f95d5a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610f0e89898d8886612a73565b83516020850160888204881415608883061715610f33576307b1daf16000526004601cfd5b60405160c8810160405260005b83811015610fe3578083018051835260208101516020840152604081015160408401526060810151606084015260808101516080840152508460888301526088810460051b8b013560a883015260c882206001860195508560005b610200811015610fd8576001821615610fb85782818b0152610fd8565b8981015160009081526020938452604090209260019290921c9101610f9b565b505050608801610f40565b50505050600160106002610ff7919061378c565b6110019190613798565b81111561103a576040517f6229572300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110af61104d8360401c63ffffffff1690565b61105d9063ffffffff168a6135ed565b60401b7fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff606084901b167fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8516171790565b9150841561113c5777ffffffffffffffffffffffffffffffffffffffffffffffff82164260c01b1791506110e98260801c63ffffffff1690565b63ffffffff166110ff8360401c63ffffffff1690565b63ffffffff161461113c576040517f7b1dafd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3360009081526014602090815260408083208e8452909152902061116290846010612ca5565b503360008181526018602090815260408083208f8452825280832080546001810182559084528284206004820401805460039092166008026101000a67ffffffffffffffff818102199093164390931602919091179055838352601582528083208f8452909152812084905560609190911b81523690601437366014016000a05050505050505050505050565b600381601081106111ff57600080fd5b0154905081565b6018602052826000526040600020602052816000526040600020818154811061122e57600080fd5b906000526020600020906004918282040191900660080292509250509054906101000a900467ffffffffffffffff1681565b60443560008060088301861061127e5763fe2549876000526004601cfd5b60c083901b60805260888386823786600882030151915060206000858360025afa9050806112ab57600080fd5b50600080517effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0400000000000000000000000000000000000000000000000000000000000000178082526002602090815260408084208a8552825280842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091558385528252808420998452988152888320939093558152908190529490942055505050565b600080603087600037602060006030600060025afa806113835763f91129696000526004601cfd5b6000517effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f010000000000000000000000000000000000000000000000000000000000000017608081815260a08c905260c08b905260308a60e037603088609083013760008060c083600a5afa925082611405576309bde3396000526004601cfd5b6028861061141b5763fe2549876000526004601cfd5b6000602882015278200000000000000000000000000000000000000000000000008152600881018b905285810151935060308a8237603081019b909b52505060509098207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0500000000000000000000000000000000000000000000000000000000000000176000818152600260209081526040808320868452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915584845282528083209583529481528482209a909a559081528089529190912096909655505050505050565b6014602052826000526040600020602052816000526040600020816010811061153a57600080fd5b0154925083915050565b73ffffffffffffffffffffffffffffffffffffffff891660009081526015602090815260408083208b845290915290205467ffffffffffffffff8116156115b7576040517fc334f06900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000786115e28260c01c90565b6115f69067ffffffffffffffff1642613798565b1161162d576040517f55d4cbf900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006116398b8b610737565b905061165287878360208c01356109cf6109ca8e6134e0565b8015611670575061167084848360208901356109cf6109ca8b6134e0565b6116a6576040517f09bde33900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8760400135896040516020016116bc91906135af565b6040516020818303038152906040528051906020012014611709576040517f1968a90200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84602001358860200135600161171f91906135ed565b141580611751575060016117398360601c63ffffffff1690565b61174391906137af565b63ffffffff16856020013514155b15611788576040517f9a3b119900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61179689610ae68780613605565b61179f896120ec565b60006117aa8a612bc6565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f020000000000000000000000000000000000000000000000000000000000000017905060006118018460a01c63ffffffff1690565b67ffffffffffffffff169050600160026000848152602001908152602001600020600083815260200190815260200160002060006101000a81548160ff021916908315150217905550601760008e73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008d815260200190815260200160002054600160008481526020019081526020016000206000838152602001908152602001600020819055506118d38460801c63ffffffff1690565b600083815260208190526040902063ffffffff9190911690556118f78d8d81612894565b50505050505050505050505050565b6000828152600260209081526040808320848452909152812054819060ff1661198f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f7072652d696d616765206d757374206578697374000000000000000000000000604482015260640160405180910390fd5b50600083815260208181526040909120546119ab8160086135ed565b6119b68560206135ed565b106119d457836119c78260086135ed565b6119d19190613798565b91505b506000938452600160209081526040808620948652939052919092205492909150565b604435600080600883018610611a155763fe2549876000526004601cfd5b60c083901b6080526088838682378087017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80151908490207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f02000000000000000000000000000000000000000000000000000000000000001760008181526002602090815260408083208b8452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915584845282528083209a83529981528982209390935590815290819052959095209190915550505050565b6000611b0b8686610737565b9050611b2483838360208801356109cf6109ca8a6134e0565b611b5a576040517f09bde33900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602084013515611b96576040517f9a3b119900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611b9e612ce3565b611bac81610ae68780613605565b611bb5816120ec565b846040013581604051602001611bcb91906135af565b6040516020818303038152906040528051906020012003611c18576040517f9843145b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff87166000908152601560209081526040808320898452909152902080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166001179055611c7c878733612894565b50505050505050565b6703782dace9d90000341015611cc7576040517fe92c469f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b333214611d00576040517fba092d1600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611d0b8160086137d4565b63ffffffff168263ffffffff1610611d4f576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000027108163ffffffff161015611daf576040517f7b1dafd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000818152601560209081526040808320878452825280832080547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff1660a09790971b7fffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffffff169690961760809590951b949094179094558251808401845282815280850186815260138054600181018255908452915160029092027f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a0908101805473ffffffffffffffffffffffffffffffffffffffff9094167fffffffffffffffffffffffff000000000000000000000000000000000000000090941693909317909255517f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a0919091015590815260168352818120938152929091529020349055565b6000816000015182602001518360400151604051602001611f13939291906137fc565b604051602081830303815290604052805190602001209050919050565b60008160005b6010811015611f84578060051b880135600186831c1660018114611f695760008481526020839052604090209350611f7a565b600082815260208590526040902093505b5050600101611f36565b5090931495945050505050565b6088815114611f9f57600080fd5b6020810160208301612020565b8260031b8201518060001a8160011a60081b178160021a60101b8260031a60181b17178160041a60201b8260051a60281b178260061a60301b8360071a60381b171717905061201a81612005868560059190911b015190565b1867ffffffffffffffff16600586901b840152565b50505050565b61202c60008383611fac565b61203860018383611fac565b61204460028383611fac565b61205060038383611fac565b61205c60048383611fac565b61206860058383611fac565b61207460068383611fac565b61208060078383611fac565b61208c60088383611fac565b61209860098383611fac565b6120a4600a8383611fac565b6120b0600b8383611fac565b6120bc600c8383611fac565b6120c8600d8383611fac565b6120d4600e8383611fac565b6120e0600f8383611fac565b61201a60108383611fac565b6040805178010000000000008082800000000000808a8000000080008000602082015279808b00000000800000018000000080008081800000000000800991810191909152788a00000000000000880000000080008009000000008000000a60608201527b8000808b800000000000008b8000000000008089800000000000800360808201527f80000000000080028000000000000080000000000000800a800000008000000a60a08201527f800000008000808180000000000080800000000080000001800000008000800860c082015260009060e00160405160208183030381529060405290506020820160208201612774565b6102808101516101e082015161014083015160a0840151845118189118186102a082015161020083015161016084015160c0850151602086015118189118186102c083015161022084015161018085015160e0860151604087015118189118186102e08401516102408501516101a0860151610100870151606088015118189118186103008501516102608601516101c0870151610120880151608089015118189118188084603f1c61229f8660011b67ffffffffffffffff1690565b18188584603f1c6122ba8660011b67ffffffffffffffff1690565b18188584603f1c6122d58660011b67ffffffffffffffff1690565b181895508483603f1c6122f28560011b67ffffffffffffffff1690565b181894508387603f1c61230f8960011b67ffffffffffffffff1690565b60208b01518b51861867ffffffffffffffff168c5291189190911897508118600181901b603f9190911c18935060c08801518118601481901c602c9190911b1867ffffffffffffffff1660208901526101208801518718602c81901c60149190911b1867ffffffffffffffff1660c08901526102c08801518618600381901c603d9190911b1867ffffffffffffffff166101208901526101c08801518718601981901c60279190911b1867ffffffffffffffff166102c08901526102808801518218602e81901c60129190911b1867ffffffffffffffff166101c089015260408801518618600281901c603e9190911b1867ffffffffffffffff166102808901526101808801518618601581901c602b9190911b1867ffffffffffffffff1660408901526101a08801518518602781901c60199190911b1867ffffffffffffffff166101808901526102608801518718603881901c60089190911b1867ffffffffffffffff166101a08901526102e08801518518600881901c60389190911b1867ffffffffffffffff166102608901526101e08801518218601781901c60299190911b1867ffffffffffffffff166102e089015260808801518718602581901c601b9190911b1867ffffffffffffffff166101e08901526103008801518718603281901c600e9190911b1867ffffffffffffffff1660808901526102a08801518118603e81901c60029190911b1867ffffffffffffffff166103008901526101008801518518600981901c60379190911b1867ffffffffffffffff166102a08901526102008801518118601381901c602d9190911b1867ffffffffffffffff1661010089015260a08801518218601c81901c60249190911b1867ffffffffffffffff1661020089015260608801518518602481901c601c9190911b1867ffffffffffffffff1660a08901526102408801518518602b81901c60159190911b1867ffffffffffffffff1660608901526102208801518618603181901c600f9190911b1867ffffffffffffffff166102408901526101608801518118603681901c600a9190911b1867ffffffffffffffff166102208901525060e08701518518603a81901c60069190911b1867ffffffffffffffff166101608801526101408701518118603d81901c60039190911b1867ffffffffffffffff1660e0880152505067ffffffffffffffff81166101408601525b5050505050565b600582811b8201805160018501831b8401805160028701851b8601805160038901871b8801805160048b0190981b8901805167ffffffffffffffff861985168918811690995283198a16861889169096528819861683188816909352841986168818871690528419831684189095169052919391929190611c7c565b61270e600082612687565b612719600582612687565b612724600a82612687565b61272f600f82612687565b61273a601482612687565b50565b612746816121e2565b61274f81612703565b600383901b820151815160c09190911c9061201a90821867ffffffffffffffff168352565b6127806000828461273d565b61278c6001828461273d565b6127986002828461273d565b6127a46003828461273d565b6127b06004828461273d565b6127bc6005828461273d565b6127c86006828461273d565b6127d46007828461273d565b6127e06008828461273d565b6127ec6009828461273d565b6127f8600a828461273d565b612804600b828461273d565b612810600c828461273d565b61281c600d828461273d565b612828600e828461273d565b612834600f828461273d565b6128406010828461273d565b61284c6011828461273d565b6128586012828461273d565b6128646013828461273d565b6128706014828461273d565b61287c6015828461273d565b6128886016828461273d565b61201a6017828461273d565b73ffffffffffffffffffffffffffffffffffffffff83811660009081526016602090815260408083208684529091528082208054908390559051909284169083908381818185875af1925050503d806000811461290d576040519150601f19603f3d011682016040523d82523d6000602084013e612912565b606091505b5050905080612680576040517f83e6cc6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f01000000000000000000000000000000000000000000000000000000000000007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8316176129f3818360408051600093845233602052918152606090922091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790565b9392505050565b6060604051905081602082018181018286833760888306808015612a435760888290038501848101848103803687375060806001820353506001845160001a1784538652612a5a565b608836843760018353608060878401536088850186525b5050505050601f19603f82510116810160405292915050565b6000612a858260a01c63ffffffff1690565b67ffffffffffffffff1690506000612aa38360801c63ffffffff1690565b63ffffffff1690506000612abd8460401c63ffffffff1690565b63ffffffff169050600883108015612ad3575080155b15612b075760c082901b6000908152883560085283513382526017602090815260408084208a855290915290912055612bbc565b60088310158015612b25575080612b1f600885613798565b93508310155b8015612b395750612b3687826135ed565b83105b15612bbc576000612b4a8285613798565b905087612b588260206135ed565b10158015612b64575085155b15612b9b576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3360009081526017602090815260408083208a845290915290209089013590555b5050505050505050565b6000612c49565b66ff00ff00ff00ff8160081c1667ff00ff00ff00ff00612bf78360081b67ffffffffffffffff1690565b1617905065ffff0000ffff8160101c1667ffff0000ffff0000612c248360101b67ffffffffffffffff1690565b1617905060008160201c612c428360201b67ffffffffffffffff1690565b1792915050565b60808201516020830190612c6190612bcd565b612bcd565b6040820151612c6f90612bcd565b60401b17612c87612c5c60018460059190911b015190565b825160809190911b90612c9990612bcd565b60c01b17179392505050565b8260108101928215612cd3579160200282015b82811115612cd3578251825591602001919060010190612cb8565b50612cdf929150612cfb565b5090565b6040518060200160405280612cf6612d10565b905290565b5b80821115612cdf5760008155600101612cfc565b6040518061032001604052806019906020820280368337509192915050565b600060208284031215612d4157600080fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114612d6c57600080fd5b919050565b60008060408385031215612d8457600080fd5b612d8d83612d48565b946020939093013593505050565b60008083601f840112612dad57600080fd5b50813567ffffffffffffffff811115612dc557600080fd5b602083019150836020828501011115612ddd57600080fd5b9250929050565b60008060008060608587031215612dfa57600080fd5b84359350612e0a60208601612d48565b9250604085013567ffffffffffffffff811115612e2657600080fd5b612e3287828801612d9b565b95989497509550505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610320810167ffffffffffffffff81118282101715612e9157612e91612e3e565b60405290565b6040516060810167ffffffffffffffff81118282101715612e9157612e91612e3e565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715612f0157612f01612e3e565b604052919050565b6000610320808385031215612f1d57600080fd5b604051602080820167ffffffffffffffff8382108183111715612f4257612f42612e3e565b8160405283955087601f880112612f5857600080fd5b612f60612e6d565b9487019491508188861115612f7457600080fd5b875b86811015612f9c5780358381168114612f8f5760008081fd5b8452928401928401612f76565b50909352509295945050505050565b600060608284031215612fbd57600080fd5b50919050565b60008083601f840112612fd557600080fd5b50813567ffffffffffffffff811115612fed57600080fd5b6020830191508360208260051b8501011115612ddd57600080fd5b60008060008060008060008060006103e08a8c03121561302757600080fd5b6130308a612d48565b985060208a013597506130468b60408c01612f09565b96506103608a013567ffffffffffffffff8082111561306457600080fd5b6130708d838e01612fab565b97506103808c013591508082111561308757600080fd5b6130938d838e01612fc3565b90975095506103a08c01359150808211156130ad57600080fd5b6130b98d838e01612fab565b94506103c08c01359150808211156130d057600080fd5b506130dd8c828d01612fc3565b915080935050809150509295985092959850929598565b600080600080600060a0868803121561310c57600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b60005b8381101561314a578181015183820152602001613132565b8381111561201a5750506000910152565b602081526000825180602084015261317a81604085016020870161312f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b600080604083850312156131bf57600080fd5b50508035926020909101359150565b600080600080600080600060a0888a0312156131e957600080fd5b8735965060208801359550604088013567ffffffffffffffff8082111561320f57600080fd5b61321b8b838c01612d9b565b909750955060608a013591508082111561323457600080fd5b506132418a828b01612fc3565b9094509250506080880135801515811461325a57600080fd5b8091505092959891949750929550565b60008060006060848603121561327f57600080fd5b61328884612d48565b95602085013595506040909401359392505050565b6000806000604084860312156132b257600080fd5b83359250602084013567ffffffffffffffff8111156132d057600080fd5b6132dc86828701612d9b565b9497909650939450505050565b600080600080600080600060a0888a03121561330457600080fd5b8735965060208801359550604088013567ffffffffffffffff8082111561332a57600080fd5b6133368b838c01612d9b565b909750955060608a013591508082111561334f57600080fd5b5061335c8a828b01612d9b565b989b979a50959894979596608090950135949350505050565b60008060008060006080868803121561338d57600080fd5b61339686612d48565b945060208601359350604086013567ffffffffffffffff808211156133ba57600080fd5b6133c689838a01612fab565b945060608801359150808211156133dc57600080fd5b506133e988828901612fc3565b969995985093965092949392505050565b803563ffffffff81168114612d6c57600080fd5b60008060006060848603121561342357600080fd5b83359250613433602085016133fa565b9150613441604085016133fa565b90509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036134d9576134d9613479565b5060010190565b6000606082360312156134f257600080fd5b6134fa612e97565b823567ffffffffffffffff8082111561351257600080fd5b9084019036601f83011261352557600080fd5b813560208282111561353957613539612e3e565b613569817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f85011601612eba565b9250818352368183860101111561357f57600080fd5b81818501828501376000918301810191909152908352848101359083015250604092830135928101929092525090565b81516103208201908260005b60198110156135e457825167ffffffffffffffff168252602092830192909101906001016135bb565b50505092915050565b6000821982111561360057613600613479565b500190565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261363a57600080fd5b83018035915067ffffffffffffffff82111561365557600080fd5b602001915036819003821315612ddd57600080fd5b600181815b808511156136c357817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156136a9576136a9613479565b808516156136b657918102915b93841c939080029061366f565b509250929050565b6000826136da57506001613786565b816136e757506000613786565b81600181146136fd576002811461370757613723565b6001915050613786565b60ff84111561371857613718613479565b50506001821b613786565b5060208310610133831016604e8410600b8410161715613746575081810a613786565b613750838361366a565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561378257613782613479565b0290505b92915050565b60006129f383836136cb565b6000828210156137aa576137aa613479565b500390565b600063ffffffff838116908316818110156137cc576137cc613479565b039392505050565b600063ffffffff8083168185168083038211156137f3576137f3613479565b01949350505050565b6000845161380e81846020890161312f565b9190910192835250602082015260400191905056fea164736f6c634300080f000a"; bytes internal constant mipsCode = - hex"608060405234801561001057600080fd5b506004361061004c5760003560e01c8063155633fe1461005157806354fd4d50146100765780637dc0d1d0146100bf578063e14ced3214610103575b600080fd5b61005c634000000081565b60405163ffffffff90911681526020015b60405180910390f35b6100b26040518060400160405280600c81526020017f312e312e302d626574612e31000000000000000000000000000000000000000081525081565b60405161006d9190611e16565b60405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003bd7e801e51d48c5d94ea68e8b801dffc275de7516815260200161006d565b610116610111366004611ed2565b610124565b60405190815260200161006d565b600061012e611d8c565b6080811461013b57600080fd5b6040516106001461014b57600080fd5b6084871461015857600080fd5b6101a4851461016657600080fd5b8635608052602087013560a052604087013560e090811c60c09081526044890135821c82526048890135821c61010052604c890135821c610120526050890135821c61014052605489013590911c61016052605888013560f890811c610180526059890135901c6101a052605a880135901c6101c0526102006101e0819052606288019060005b602081101561021157823560e01c82526004909201916020909101906001016101ed565b5050508061012001511561022f5761022761066f565b915050610666565b6101408101805160010167ffffffffffffffff1690526060810151600090610257908261078b565b9050603f601a82901c16600281148061027657508063ffffffff166003145b156102cb5760006002836303ffffff1663ffffffff16901b846080015163f0000000161790506102c08263ffffffff166002146102b457601f6102b7565b60005b60ff1682610847565b945050505050610666565b6101608301516000908190601f601086901c81169190601587901c16602081106102f7576102f7611f46565b602002015192508063ffffffff8516158061031857508463ffffffff16601c145b1561034f578661016001518263ffffffff166020811061033a5761033a611f46565b6020020151925050601f600b86901c1661040b565b60208563ffffffff1610156103b1578463ffffffff16600c148061037957508463ffffffff16600d145b8061038a57508463ffffffff16600e145b1561039b578561ffff16925061040b565b6103aa8661ffff166010610938565b925061040b565b60288563ffffffff161015806103cd57508463ffffffff166022145b806103de57508463ffffffff166026145b1561040b578661016001518263ffffffff166020811061040057610400611f46565b602002015192508190505b60048563ffffffff1610158015610428575060088563ffffffff16105b8061043957508463ffffffff166001145b156104585761044a858784876109ab565b975050505050505050610666565b63ffffffff60006020878316106104bd576104788861ffff166010610938565b9095019463fffffffc861661048e81600161078b565b915060288863ffffffff16101580156104ae57508763ffffffff16603014155b156104bb57809250600093505b505b60006104cb89888885610bbb565b63ffffffff9081169150603f8a169089161580156104f0575060088163ffffffff1610155b80156105025750601c8163ffffffff16105b156105df578063ffffffff166008148061052257508063ffffffff166009145b15610559576105478163ffffffff1660081461053e5785610541565b60005b89610847565b9b505050505050505050505050610666565b8063ffffffff16600a0361057957610547858963ffffffff8a161561134b565b8063ffffffff16600b0361059a57610547858963ffffffff8a16151561134b565b8063ffffffff16600c036105b1576105478d611431565b60108163ffffffff16101580156105ce5750601c8163ffffffff16105b156105df5761054781898988611968565b8863ffffffff1660381480156105fa575063ffffffff861615155b1561062f5760018b61016001518763ffffffff166020811061061e5761061e611f46565b63ffffffff90921660209290920201525b8363ffffffff1663ffffffff1461064c5761064c84600184611c3f565b6106588583600161134b565b9b5050505050505050505050505b95945050505050565b60408051608051815260a051602082015260dc519181019190915260fc51604482015261011c51604882015261013c51604c82015261015c51605082015261017c5160548201526101805161019f5160588301526101a0516101bf5160598401526101d851605a840152600092610200929091606283019190855b602081101561070e57601c86015184526020909501946004909301926001016106ea565b506000835283830384a060009450806001811461072e5760039550610756565b828015610746576001811461074f5760029650610754565b60009650610754565b600196505b505b50505081900390207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660f89190911b17919050565b60008061079783611ce3565b905060038416156107a757600080fd5b6020810190358460051c8160005b601b81101561080d5760208501943583821c60011680156107dd57600181146107f257610803565b60008481526020839052604090209350610803565b600082815260208590526040902093505b50506001016107b5565b50608051915081811461082857630badf00d60005260206000fd5b5050601f94909416601c0360031b9390931c63ffffffff169392505050565b6000610851611d8c565b60809050806060015160040163ffffffff16816080015163ffffffff16146108da576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6a756d7020696e2064656c617920736c6f74000000000000000000000000000060448201526064015b60405180910390fd5b60608101805160808301805163ffffffff90811690935285831690529085161561093057806008018261016001518663ffffffff166020811061091f5761091f611f46565b63ffffffff90921660209290920201525b61066661066f565b600063ffffffff8381167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80850183169190911c821615159160016020869003821681901b830191861691821b92911b0182610995576000610997565b815b90861663ffffffff16179250505092915050565b60006109b5611d8c565b608090506000816060015160040163ffffffff16826080015163ffffffff1614610a3b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6272616e636820696e2064656c617920736c6f7400000000000000000000000060448201526064016108d1565b8663ffffffff1660041480610a5657508663ffffffff166005145b15610ad25760008261016001518663ffffffff1660208110610a7a57610a7a611f46565b602002015190508063ffffffff168563ffffffff16148015610aa257508763ffffffff166004145b80610aca57508063ffffffff168563ffffffff1614158015610aca57508763ffffffff166005145b915050610b4f565b8663ffffffff16600603610aef5760008460030b13159050610b4f565b8663ffffffff16600703610b0b5760008460030b139050610b4f565b8663ffffffff16600103610b4f57601f601087901c166000819003610b345760008560030b1291505b8063ffffffff16600103610b4d5760008560030b121591505b505b606082018051608084015163ffffffff169091528115610b95576002610b7a8861ffff166010610938565b63ffffffff90811690911b8201600401166080840152610ba7565b60808301805160040163ffffffff1690525b610baf61066f565b98975050505050505050565b6000603f601a86901c16801580610bea575060088163ffffffff1610158015610bea5750600f8163ffffffff16105b1561104057603f86168160088114610c315760098114610c3a57600a8114610c4357600b8114610c4c57600c8114610c5557600d8114610c5e57600e8114610c6757610c6c565b60209150610c6c565b60219150610c6c565b602a9150610c6c565b602b9150610c6c565b60249150610c6c565b60259150610c6c565b602691505b508063ffffffff16600003610c935750505063ffffffff8216601f600686901c161b611343565b8063ffffffff16600203610cb95750505063ffffffff8216601f600686901c161c611343565b8063ffffffff16600303610cef57601f600688901c16610ce563ffffffff8716821c6020839003610938565b9350505050611343565b8063ffffffff16600403610d115750505063ffffffff8216601f84161b611343565b8063ffffffff16600603610d335750505063ffffffff8216601f84161c611343565b8063ffffffff16600703610d6657610d5d8663ffffffff168663ffffffff16901c87602003610938565b92505050611343565b8063ffffffff16600803610d7e578592505050611343565b8063ffffffff16600903610d96578592505050611343565b8063ffffffff16600a03610dae578592505050611343565b8063ffffffff16600b03610dc6578592505050611343565b8063ffffffff16600c03610dde578592505050611343565b8063ffffffff16600f03610df6578592505050611343565b8063ffffffff16601003610e0e578592505050611343565b8063ffffffff16601103610e26578592505050611343565b8063ffffffff16601203610e3e578592505050611343565b8063ffffffff16601303610e56578592505050611343565b8063ffffffff16601803610e6e578592505050611343565b8063ffffffff16601903610e86578592505050611343565b8063ffffffff16601a03610e9e578592505050611343565b8063ffffffff16601b03610eb6578592505050611343565b8063ffffffff16602003610ecf57505050828201611343565b8063ffffffff16602103610ee857505050828201611343565b8063ffffffff16602203610f0157505050818303611343565b8063ffffffff16602303610f1a57505050818303611343565b8063ffffffff16602403610f3357505050828216611343565b8063ffffffff16602503610f4c57505050828217611343565b8063ffffffff16602603610f6557505050828218611343565b8063ffffffff16602703610f7f5750505082821719611343565b8063ffffffff16602a03610fb0578460030b8660030b12610fa1576000610fa4565b60015b60ff1692505050611343565b8063ffffffff16602b03610fd8578463ffffffff168663ffffffff1610610fa1576000610fa4565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f696e76616c696420696e737472756374696f6e0000000000000000000000000060448201526064016108d1565b50610fd8565b8063ffffffff16601c036110c457603f8616600281900361106657505050828202611343565b8063ffffffff166020148061108157508063ffffffff166021145b1561103a578063ffffffff16602003611098579419945b60005b63800000008716156110ba576401fffffffe600197881b16960161109b565b9250611343915050565b8063ffffffff16600f036110e657505065ffffffff0000601083901b16611343565b8063ffffffff166020036111225761111a8560031660080260180363ffffffff168463ffffffff16901c60ff166008610938565b915050611343565b8063ffffffff166021036111575761111a8560021660080260100363ffffffff168463ffffffff16901c61ffff166010610938565b8063ffffffff1660220361118657505063ffffffff60086003851602811681811b198416918316901b17611343565b8063ffffffff1660230361119d5782915050611343565b8063ffffffff166024036111cf578460031660080260180363ffffffff168363ffffffff16901c60ff16915050611343565b8063ffffffff16602503611202578460021660080260100363ffffffff168363ffffffff16901c61ffff16915050611343565b8063ffffffff1660260361123457505063ffffffff60086003851602601803811681811c198416918316901c17611343565b8063ffffffff1660280361126a57505060ff63ffffffff60086003861602601803811682811b9091188316918416901b17611343565b8063ffffffff166029036112a157505061ffff63ffffffff60086002861602601003811682811b9091188316918416901b17611343565b8063ffffffff16602a036112d057505063ffffffff60086003851602811681811c198316918416901c17611343565b8063ffffffff16602b036112e75783915050611343565b8063ffffffff16602e0361131957505063ffffffff60086003851602601803811681811b198316918416901b17611343565b8063ffffffff166030036113305782915050611343565b8063ffffffff16603803610fd857839150505b949350505050565b6000611355611d8c565b506080602063ffffffff8616106113c8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f76616c696420726567697374657200000000000000000000000000000000000060448201526064016108d1565b63ffffffff8516158015906113da5750825b1561140e57838161016001518663ffffffff16602081106113fd576113fd611f46565b63ffffffff90921660209290920201525b60808101805163ffffffff8082166060850152600490910116905261066661066f565b600061143b611d8c565b506101e051604081015160808083015160a084015160c09094015191936000928392919063ffffffff8616610ffa036114b55781610fff81161561148457610fff811661100003015b8363ffffffff166000036114ab5760e08801805163ffffffff8382011690915295506114af565b8395505b50611927565b8563ffffffff16610fcd036114d05763400000009450611927565b8563ffffffff16611018036114e85760019450611927565b8563ffffffff166110960361151e57600161012088015260ff831661010088015261151161066f565b9998505050505050505050565b8563ffffffff16610fa30361178a5763ffffffff831615611927577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb63ffffffff8416016117445760006115798363fffffffc16600161078b565b60208901519091508060001a6001036115e857604080516000838152336020528d83526060902091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790505b6040808a015190517fe03110e10000000000000000000000000000000000000000000000000000000081526004810183905263ffffffff9091166024820152600090819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003bd7e801e51d48c5d94ea68e8b801dffc275de75169063e03110e1906044016040805180830381865afa158015611689573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116ad9190611f75565b915091506003861680600403828110156116c5578092505b50818610156116d2578591505b8260088302610100031c9250826008828460040303021b9250600180600883600403021b036001806008858560040303021b039150811981169050838119871617955050506117298663fffffffc16600186611c3f565b60408b018051820163ffffffff169052975061178592505050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd63ffffffff84160161177957809450611927565b63ffffffff9450600993505b611927565b8563ffffffff16610fa40361187b5763ffffffff8316600114806117b4575063ffffffff83166002145b806117c5575063ffffffff83166004145b156117d257809450611927565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa63ffffffff8416016117795760006118128363fffffffc16600161078b565b6020890151909150600384166004038381101561182d578093505b83900360089081029290921c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600193850293841b0116911b17602088015260006040880152935083611927565b8563ffffffff16610fd703611927578163ffffffff1660030361191b5763ffffffff831615806118b1575063ffffffff83166005145b806118c2575063ffffffff83166003145b156118d05760009450611927565b63ffffffff8316600114806118eb575063ffffffff83166002145b806118fc575063ffffffff83166006145b8061190d575063ffffffff83166004145b156117795760019450611927565b63ffffffff9450601693505b6101608701805163ffffffff808816604090920191909152905185821660e09091015260808801805180831660608b0152600401909116905261151161066f565b6000611972611d8c565b506080600063ffffffff8716601003611990575060c0810151611bd6565b8663ffffffff166011036119af5763ffffffff861660c0830152611bd6565b8663ffffffff166012036119c8575060a0810151611bd6565b8663ffffffff166013036119e75763ffffffff861660a0830152611bd6565b8663ffffffff16601803611a1b5763ffffffff600387810b9087900b02602081901c821660c08501521660a0830152611bd6565b8663ffffffff16601903611a4c5763ffffffff86811681871602602081901c821660c08501521660a0830152611bd6565b8663ffffffff16601a03611b0f578460030b600003611ac7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d4950533a206469766973696f6e206279207a65726f0000000000000000000060448201526064016108d1565b8460030b8660030b81611adc57611adc611f99565b0763ffffffff1660c0830152600385810b9087900b81611afe57611afe611f99565b0563ffffffff1660a0830152611bd6565b8663ffffffff16601b03611bd6578463ffffffff16600003611b8d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d4950533a206469766973696f6e206279207a65726f0000000000000000000060448201526064016108d1565b8463ffffffff168663ffffffff1681611ba857611ba8611f99565b0663ffffffff90811660c084015285811690871681611bc957611bc9611f99565b0463ffffffff1660a08301525b63ffffffff841615611c1157808261016001518563ffffffff1660208110611c0057611c00611f46565b63ffffffff90921660209290920201525b60808201805163ffffffff80821660608601526004909101169052611c3461066f565b979650505050505050565b6000611c4a83611ce3565b90506003841615611c5a57600080fd5b6020810190601f8516601c0360031b83811b913563ffffffff90911b1916178460051c60005b601b811015611cd85760208401933582821c6001168015611ca85760018114611cbd57611cce565b60008581526020839052604090209450611cce565b600082815260208690526040902094505b5050600101611c80565b505060805250505050565b60ff8116610380026101a4810190369061052401811015611d86576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f636865636b207468617420746865726520697320656e6f7567682063616c6c6460448201527f617461000000000000000000000000000000000000000000000000000000000060648201526084016108d1565b50919050565b6040805161018081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091526101608101611df2611df7565b905290565b6040518061040001604052806020906020820280368337509192915050565b600060208083528351808285015260005b81811015611e4357858101830151858201604001528201611e27565b81811115611e55576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60008083601f840112611e9b57600080fd5b50813567ffffffffffffffff811115611eb357600080fd5b602083019150836020828501011115611ecb57600080fd5b9250929050565b600080600080600060608688031215611eea57600080fd5b853567ffffffffffffffff80821115611f0257600080fd5b611f0e89838a01611e89565b90975095506020880135915080821115611f2757600080fd5b50611f3488828901611e89565b96999598509660400135949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008060408385031215611f8857600080fd5b505080516020909101519092909150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea164736f6c634300080f000a"; + hex"608060405234801561001057600080fd5b506004361061004c5760003560e01c8063155633fe1461005157806354fd4d50146100765780637dc0d1d0146100bf578063e14ced3214610103575b600080fd5b61005c634000000081565b60405163ffffffff90911681526020015b60405180910390f35b6100b26040518060400160405280600c81526020017f312e312e302d626574612e34000000000000000000000000000000000000000081525081565b60405161006d9190612378565b60405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003bd7e801e51d48c5d94ea68e8b801dffc275de7516815260200161006d565b61011661011136600461242d565b610124565b60405190815260200161006d565b600061012e6122ee565b6080811461013b57600080fd5b6040516106001461014b57600080fd5b6084871461015857600080fd5b6101a4851461016657600080fd5b8635608052602087013560a052604087013560e090811c60c09081526044890135821c82526048890135821c61010052604c890135821c610120526050890135821c61014052605489013590911c61016052605888013560f890811c610180526059890135901c6101a052605a880135901c6101c0526102006101e0819052606288019060005b602081101561021157823560e01c82526004909201916020909101906001016101ed565b5050508061012001511561022f5761022761082b565b915050610822565b6101408101805160010167ffffffffffffffff16905260006101a4905060006102618360000151846060015184610947565b9050603f601a82901c16600281148061028057508063ffffffff166003145b156102d75760006002836303ffffff1663ffffffff16901b856080015163f0000000161790506102cb858363ffffffff166002146102bf57601f6102c2565b60005b60ff16836109fb565b95505050505050610822565b6101608401516000908190601f601086901c81169190601587901c1660208110610303576103036124a1565b602002015192508063ffffffff8516158061032457508463ffffffff16601c145b1561035b578761016001518263ffffffff1660208110610346576103466124a1565b6020020151925050601f600b86901c16610417565b60208563ffffffff1610156103bd578463ffffffff16600c148061038557508463ffffffff16600d145b8061039657508463ffffffff16600e145b156103a7578561ffff169250610417565b6103b68661ffff166010610ac5565b9250610417565b60288563ffffffff161015806103d957508463ffffffff166022145b806103ea57508463ffffffff166026145b15610417578761016001518263ffffffff166020811061040c5761040c6124a1565b602002015192508190505b60048563ffffffff1610158015610434575060088563ffffffff16105b8061044557508463ffffffff166001145b156105255760006104c4896040805160808101825260008082526020820181905291810182905260608101919091526040518060800160405280836060015163ffffffff168152602001836080015163ffffffff1681526020018360a0015163ffffffff1681526020018360c0015163ffffffff168152509050919050565b90506104d9818a6101600151888a878a610b38565b805163ffffffff9081166060808c01919091526020830151821660808c01526040830151821660a08c01528201511660c08a015261051561082b565b9950505050505050505050610822565b63ffffffff6000602087831610610591576105458861ffff166010610ac5565b8a5196019563fffffffc87169061052490610561908383610947565b925060288963ffffffff161015801561058157508863ffffffff16603014155b1561058e57819350600094505b50505b600061059f89888885610d2b565b63ffffffff9081169150603f8a169089161580156105c4575060088163ffffffff1610155b80156105d65750601c8163ffffffff16105b15610793578063ffffffff16600814806105f657508063ffffffff166009145b1561062f5761061c8c8263ffffffff166008146106135786610616565b60005b8a6109fb565b9c50505050505050505050505050610822565b8063ffffffff16600a036106505761061c8c868a63ffffffff8b16156114bb565b8063ffffffff16600b036106725761061c8c868a63ffffffff8b1615156114bb565b8063ffffffff16600c036106895761061c8e611590565b60108163ffffffff16101580156106a65750601c8163ffffffff16105b156107935760006107258d6040805160808101825260008082526020820181905291810182905260608101919091526040518060800160405280836060015163ffffffff168152602001836080015163ffffffff1681526020018360a0015163ffffffff1681526020018360c0015163ffffffff168152509050919050565b905061073a818e6101600151848c8c8b6118cd565b6107778d82805163ffffffff9081166060808501919091526020830151821660808501526040830151821660a0850152909101511660c090910152565b61077f61082b565b9d5050505050505050505050505050610822565b8863ffffffff1660381480156107ae575063ffffffff861615155b156107e35760018c61016001518763ffffffff16602081106107d2576107d26124a1565b63ffffffff90921660209290920201525b8363ffffffff1663ffffffff1461080657610524610802858285611b86565b8d52505b6108138c868460016114bb565b9c505050505050505050505050505b95945050505050565b60408051608051815260a051602082015260dc519181019190915260fc51604482015261011c51604882015261013c51604c82015261015c51605082015261017c5160548201526101805161019f5160588301526101a0516101bf5160598401526101d851605a840152600092610200929091606283019190855b60208110156108ca57601c86015184526020909501946004909301926001016108a6565b506000835283830384a06000945080600181146108ea5760039550610912565b828015610902576001811461090b5760029650610910565b60009650610910565b600196505b505b50505081900390207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660f89190911b17919050565b600061095282611c28565b600383161561096057600080fd5b6020820191358360051c8160005b601b8110156109c65760208601953583821c600116801561099657600181146109ab576109bc565b600084815260208390526040902093506109bc565b600082815260208590526040902093505b505060010161096e565b508681146109dc57630badf00d60005260206000fd5b5050601f93909316601c0360031b9290921c63ffffffff169392505050565b600080610a76856040805160808101825260008082526020820181905291810182905260608101919091526040518060800160405280836060015163ffffffff168152602001836080015163ffffffff1681526020018360a0015163ffffffff1681526020018360c0015163ffffffff168152509050919050565b9050610a89818661016001518686611cc8565b805163ffffffff9081166060808801919091526020830151821660808801526040830151821660a08801528201511660c086015261082261082b565b600063ffffffff8381167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80850183169190911c821615159160016020869003821681901b830191861691821b92911b0182610b22576000610b24565b815b90861663ffffffff16179250505092915050565b6000866000015160040163ffffffff16876020015163ffffffff1614610bbf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6272616e636820696e2064656c617920736c6f7400000000000000000000000060448201526064015b60405180910390fd5b8463ffffffff1660041480610bda57508463ffffffff166005145b15610c51576000868463ffffffff1660208110610bf957610bf96124a1565b602002015190508063ffffffff168363ffffffff16148015610c2157508563ffffffff166004145b80610c4957508063ffffffff168363ffffffff1614158015610c4957508563ffffffff166005145b915050610cce565b8463ffffffff16600603610c6e5760008260030b13159050610cce565b8463ffffffff16600703610c8a5760008260030b139050610cce565b8463ffffffff16600103610cce57601f601085901c166000819003610cb35760008360030b1291505b8063ffffffff16600103610ccc5760008360030b121591505b505b8651602088015163ffffffff1688528115610d0f576002610cf48661ffff166010610ac5565b63ffffffff90811690911b8201600401166020890152610d21565b60208801805160040163ffffffff1690525b5050505050505050565b6000603f601a86901c16801580610d5a575060088163ffffffff1610158015610d5a5750600f8163ffffffff16105b156111b057603f86168160088114610da15760098114610daa57600a8114610db357600b8114610dbc57600c8114610dc557600d8114610dce57600e8114610dd757610ddc565b60209150610ddc565b60219150610ddc565b602a9150610ddc565b602b9150610ddc565b60249150610ddc565b60259150610ddc565b602691505b508063ffffffff16600003610e035750505063ffffffff8216601f600686901c161b6114b3565b8063ffffffff16600203610e295750505063ffffffff8216601f600686901c161c6114b3565b8063ffffffff16600303610e5f57601f600688901c16610e5563ffffffff8716821c6020839003610ac5565b93505050506114b3565b8063ffffffff16600403610e815750505063ffffffff8216601f84161b6114b3565b8063ffffffff16600603610ea35750505063ffffffff8216601f84161c6114b3565b8063ffffffff16600703610ed657610ecd8663ffffffff168663ffffffff16901c87602003610ac5565b925050506114b3565b8063ffffffff16600803610eee5785925050506114b3565b8063ffffffff16600903610f065785925050506114b3565b8063ffffffff16600a03610f1e5785925050506114b3565b8063ffffffff16600b03610f365785925050506114b3565b8063ffffffff16600c03610f4e5785925050506114b3565b8063ffffffff16600f03610f665785925050506114b3565b8063ffffffff16601003610f7e5785925050506114b3565b8063ffffffff16601103610f965785925050506114b3565b8063ffffffff16601203610fae5785925050506114b3565b8063ffffffff16601303610fc65785925050506114b3565b8063ffffffff16601803610fde5785925050506114b3565b8063ffffffff16601903610ff65785925050506114b3565b8063ffffffff16601a0361100e5785925050506114b3565b8063ffffffff16601b036110265785925050506114b3565b8063ffffffff1660200361103f575050508282016114b3565b8063ffffffff16602103611058575050508282016114b3565b8063ffffffff16602203611071575050508183036114b3565b8063ffffffff1660230361108a575050508183036114b3565b8063ffffffff166024036110a3575050508282166114b3565b8063ffffffff166025036110bc575050508282176114b3565b8063ffffffff166026036110d5575050508282186114b3565b8063ffffffff166027036110ef57505050828217196114b3565b8063ffffffff16602a03611120578460030b8660030b12611111576000611114565b60015b60ff16925050506114b3565b8063ffffffff16602b03611148578463ffffffff168663ffffffff1610611111576000611114565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f696e76616c696420696e737472756374696f6e000000000000000000000000006044820152606401610bb6565b50611148565b8063ffffffff16601c0361123457603f861660028190036111d6575050508282026114b3565b8063ffffffff16602014806111f157508063ffffffff166021145b156111aa578063ffffffff16602003611208579419945b60005b638000000087161561122a576401fffffffe600197881b16960161120b565b92506114b3915050565b8063ffffffff16600f0361125657505065ffffffff0000601083901b166114b3565b8063ffffffff166020036112925761128a8560031660080260180363ffffffff168463ffffffff16901c60ff166008610ac5565b9150506114b3565b8063ffffffff166021036112c75761128a8560021660080260100363ffffffff168463ffffffff16901c61ffff166010610ac5565b8063ffffffff166022036112f657505063ffffffff60086003851602811681811b198416918316901b176114b3565b8063ffffffff1660230361130d57829150506114b3565b8063ffffffff1660240361133f578460031660080260180363ffffffff168363ffffffff16901c60ff169150506114b3565b8063ffffffff16602503611372578460021660080260100363ffffffff168363ffffffff16901c61ffff169150506114b3565b8063ffffffff166026036113a457505063ffffffff60086003851602601803811681811c198416918316901c176114b3565b8063ffffffff166028036113da57505060ff63ffffffff60086003861602601803811682811b9091188316918416901b176114b3565b8063ffffffff1660290361141157505061ffff63ffffffff60086002861602601003811682811b9091188316918416901b176114b3565b8063ffffffff16602a0361144057505063ffffffff60086003851602811681811c198316918416901c176114b3565b8063ffffffff16602b0361145757839150506114b3565b8063ffffffff16602e0361148957505063ffffffff60086003851602601803811681811b198316918416901b176114b3565b8063ffffffff166030036114a057829150506114b3565b8063ffffffff1660380361114857839150505b949350505050565b600080611536866040805160808101825260008082526020820181905291810182905260608101919091526040518060800160405280836060015163ffffffff168152602001836080015163ffffffff1681526020018360a0015163ffffffff1681526020018360c0015163ffffffff168152509050919050565b905061154a81876101600151878787611d9b565b805163ffffffff9081166060808901919091526020830151821660808901526040830151821660a08901528201511660c087015261158661082b565b9695505050505050565b600061159a6122ee565b506101e051604081015160808083015160a084015160c090940151919390916000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00663ffffffff87160161160d576115f885858960e00151611e6b565b63ffffffff1660e08a015290925090506117f6565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03363ffffffff87160161164657634000000091506117f6565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefe863ffffffff87160161167c57600191506117f6565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef6a63ffffffff8716016116d057600161012088015260ff85166101008801526116c361082b565b9998505050505050505050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff05d63ffffffff871601611754576020870151604088015161173d918791879187918e7f0000000000000000000000003bd7e801e51d48c5d94ea68e8b801dffc275de756105248f51611ec7565b8a5263ffffffff1660408a015290925090506117f6565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff05c63ffffffff8716016117b9576020870151604088015161179f918791879187916105248d51612105565b63ffffffff1660408b015260208a015290925090506117f6565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02963ffffffff8716016117f6576117f085856121fb565b90925090505b6000611870886040805160808101825260008082526020820181905291810182905260608101919091526040518060800160405280836060015163ffffffff168152602001836080015163ffffffff1681526020018360a0015163ffffffff1681526020018360c0015163ffffffff168152509050919050565b90506118838189610160015185856122b0565b805163ffffffff9081166060808b01919091526020830151821660808b01526040830151821660a08b01528201511660c08901526118bf61082b565b9a9950505050505050505050565b60008463ffffffff166010036118e857506060860151611b2e565b8463ffffffff166011036119075763ffffffff84166060880152611b2e565b8463ffffffff1660120361192057506040860151611b2e565b8463ffffffff1660130361193f5763ffffffff84166040880152611b2e565b8463ffffffff166018036119735763ffffffff600385810b9085900b02602081901c821660608a0152166040880152611b2e565b8463ffffffff166019036119a45763ffffffff84811681851602602081901c821660608a0152166040880152611b2e565b8463ffffffff16601a03611a67578260030b600003611a1f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d4950533a206469766973696f6e206279207a65726f000000000000000000006044820152606401610bb6565b8260030b8460030b81611a3457611a346124d0565b0763ffffffff166060880152600383810b9085900b81611a5657611a566124d0565b0563ffffffff166040880152611b2e565b8463ffffffff16601b03611b2e578263ffffffff16600003611ae5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d4950533a206469766973696f6e206279207a65726f000000000000000000006044820152606401610bb6565b8263ffffffff168463ffffffff1681611b0057611b006124d0565b0663ffffffff908116606089015283811690851681611b2157611b216124d0565b0463ffffffff1660408801525b63ffffffff821615611b645780868363ffffffff1660208110611b5357611b536124a1565b63ffffffff90921660209290920201525b50505060208401805163ffffffff808216909652600401909416909352505050565b6000611b9183611c28565b6003841615611b9f57600080fd5b6020830192601f8516601c0360031b83811b913563ffffffff90911b1916178460051c60005b601b811015611c1d5760208601953582821c6001168015611bed5760018114611c0257611c13565b60008581526020839052604090209450611c13565b600082815260208690526040902094505b5050600101611bc5565b509095945050505050565b36611c358261038061252e565b811015611cc4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f636865636b207468617420746865726520697320656e6f7567682063616c6c6460448201527f61746100000000000000000000000000000000000000000000000000000000006064820152608401610bb6565b5050565b836000015160040163ffffffff16846020015163ffffffff1614611d48576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6a756d7020696e2064656c617920736c6f7400000000000000000000000000006044820152606401610bb6565b835160208501805163ffffffff9081168752838116909152831615611d945780600801848463ffffffff1660208110611d8357611d836124a1565b63ffffffff90921660209290920201525b5050505050565b60208363ffffffff1610611e0b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f76616c69642072656769737465720000000000000000000000000000000000006044820152606401610bb6565b63ffffffff831615801590611e1d5750805b15611e4c5781848463ffffffff1660208110611e3b57611e3b6124a1565b63ffffffff90921660209290920201525b5050505060208101805163ffffffff8082169093526004019091169052565b6000808284610fff811615611e9757611e8a610fff8216611000612546565b611e94908261256b565b90505b8663ffffffff16600003611eb957849350611eb2818361256b565b9150611ebd565b8693505b5093509350939050565b600080868363ffffffff8d16156120f5577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb63ffffffff8e16016120b4576000611f18868e63fffffffc1689610947565b90508a60001a600103611f81576040805160008d8152336020528b83526060902091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0100000000000000000000000000000000000000000000000000000000000000179a505b6040517fe03110e1000000000000000000000000000000000000000000000000000000008152600481018c905263ffffffff8b166024820152600090819073ffffffffffffffffffffffffffffffffffffffff8b169063e03110e1906044016040805180830381865afa158015611ffc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120209190612593565b9150915060038f168060040382811015612038578092505b50818f1015612045578e91505b8260088302610100031c9250826008828460040303021b9250600180600883600403021b036001806008858560040303021b0391508119811690508381198616179450505061209b8f63fffffffc168a85611b86565b93506120a7818661256b565b94508096505050506120f5565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd63ffffffff8e16016120e9578a93506120f5565b63ffffffff9350600992505b9950995099509995505050505050565b600080858563ffffffff8b1660011480612125575063ffffffff8b166002145b80612136575063ffffffff8b166004145b15612143578893506121ed565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa63ffffffff8c16016121e1576000612183868c63fffffffc1689610947565b90508860038c166004038b81101561219957809b505b8b965086900360089081029290921c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600193880293841b0116911b179150600090506121ed565b63ffffffff9350600992505b975097509750979350505050565b60008063ffffffff831660030361229e5763ffffffff84161580612225575063ffffffff84166005145b80612236575063ffffffff84166003145b1561224457600091506122a9565b63ffffffff84166001148061225f575063ffffffff84166002145b80612270575063ffffffff84166006145b80612281575063ffffffff84166004145b1561228f57600191506122a9565b5063ffffffff905060096122a9565b5063ffffffff905060165b9250929050565b63ffffffff808316604085015281811660e0850152602085015190811685526122da90600461256b565b63ffffffff16602090940193909352505050565b6040805161018081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091526101608101612354612359565b905290565b6040518061040001604052806020906020820280368337509192915050565b600060208083528351808285015260005b818110156123a557858101830151858201604001528201612389565b818111156123b7576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60008083601f8401126123fd57600080fd5b50813567ffffffffffffffff81111561241557600080fd5b6020830191508360208285010111156122a957600080fd5b60008060008060006060868803121561244557600080fd5b853567ffffffffffffffff8082111561245d57600080fd5b61246989838a016123eb565b9097509550602088013591508082111561248257600080fd5b5061248f888289016123eb565b96999598509660400135949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115612541576125416124ff565b500190565b600063ffffffff83811690831681811015612563576125636124ff565b039392505050565b600063ffffffff80831681851680830382111561258a5761258a6124ff565b01949350505050565b600080604083850312156125a657600080fd5b50508051602090910151909290915056fea164736f6c634300080f000a"; bytes internal constant anchorStateRegistryCode = hex"608060405234801561001057600080fd5b50600436106100675760003560e01c8063838c2d1e11610050578063838c2d1e146100fa578063c303f0df14610104578063f2b4e6171461011757600080fd5b806354fd4d501461006c5780637258a807146100be575b600080fd5b6100a86040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100b5919061085c565b60405180910390f35b6100e56100cc36600461088b565b6001602081905260009182526040909120805491015482565b604080519283526020830191909152016100b5565b61010261015b565b005b61010261011236600461094f565b6105d4565b60405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000008b71b41d4dbeb2b6821d44692d3facaaf77480bb1681526020016100b5565b600033905060008060008373ffffffffffffffffffffffffffffffffffffffff1663fa24f7436040518163ffffffff1660e01b8152600401600060405180830381865afa1580156101b0573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526101f69190810190610a68565b92509250925060007f0000000000000000000000008b71b41d4dbeb2b6821d44692d3facaaf77480bb73ffffffffffffffffffffffffffffffffffffffff16635f0150cb8585856040518463ffffffff1660e01b815260040161025b93929190610b39565b6040805180830381865afa158015610277573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061029b9190610b67565b5090508473ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610384576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f416e63686f72537461746552656769737472793a206661756c7420646973707560448201527f74652067616d65206e6f7420726567697374657265642077697468206661637460648201527f6f72790000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b600160008563ffffffff1663ffffffff168152602001908152602001600020600101548573ffffffffffffffffffffffffffffffffffffffff16638b85902b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104169190610bc7565b11610422575050505050565b60028573ffffffffffffffffffffffffffffffffffffffff1663200d2ed26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561046f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104939190610c0f565b60028111156104a4576104a4610be0565b146104b0575050505050565b60405180604001604052806105308773ffffffffffffffffffffffffffffffffffffffff1663bcef3b556040518163ffffffff1660e01b8152600401602060405180830381865afa158015610509573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061052d9190610bc7565b90565b81526020018673ffffffffffffffffffffffffffffffffffffffff16638b85902b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610580573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105a49190610bc7565b905263ffffffff909416600090815260016020818152604090922086518155959091015194019390935550505050565b600054610100900460ff16158080156105f45750600054600160ff909116105b8061060e5750303b15801561060e575060005460ff166001145b61069a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a6564000000000000000000000000000000000000606482015260840161037b565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905580156106f857600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b60005b825181101561075e57600083828151811061071857610718610c30565b60209081029190910181015180820151905163ffffffff16600090815260018084526040909120825181559190920151910155508061075681610c5f565b9150506106fb565b5080156107c257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b60005b838110156107fd5781810151838201526020016107e5565b8381111561080c576000848401525b50505050565b6000815180845261082a8160208601602086016107e2565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061086f6020830184610812565b9392505050565b63ffffffff8116811461088857600080fd5b50565b60006020828403121561089d57600080fd5b813561086f81610876565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff811182821017156108fa576108fa6108a8565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610947576109476108a8565b604052919050565b6000602080838503121561096257600080fd5b823567ffffffffffffffff8082111561097a57600080fd5b818501915085601f83011261098e57600080fd5b8135818111156109a0576109a06108a8565b6109ae848260051b01610900565b818152848101925060609182028401850191888311156109cd57600080fd5b938501935b82851015610a5c57848903818112156109eb5760008081fd5b6109f36108d7565b86356109fe81610876565b815260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08301811315610a325760008081fd5b610a3a6108d7565b888a0135815290880135898201528189015285525093840193928501926109d2565b50979650505050505050565b600080600060608486031215610a7d57600080fd5b8351610a8881610876565b60208501516040860151919450925067ffffffffffffffff80821115610aad57600080fd5b818601915086601f830112610ac157600080fd5b815181811115610ad357610ad36108a8565b610b0460207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610900565b9150808252876020828501011115610b1b57600080fd5b610b2c8160208401602086016107e2565b5080925050509250925092565b63ffffffff84168152826020820152606060408201526000610b5e6060830184610812565b95945050505050565b60008060408385031215610b7a57600080fd5b825173ffffffffffffffffffffffffffffffffffffffff81168114610b9e57600080fd5b602084015190925067ffffffffffffffff81168114610bbc57600080fd5b809150509250929050565b600060208284031215610bd957600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600060208284031215610c2157600080fd5b81516003811061086f57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610cb7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b506001019056fea164736f6c634300080f000a"; bytes internal constant acc27Code = - hex"6080604052600436106102f25760003560e01c806370872aa51161018f578063c6f0308c116100e1578063ec5e63081161008a578063fa24f74311610064578063fa24f74314610b18578063fa315aa914610b3c578063fe2bbeb214610b6f57600080fd5b8063ec5e630814610a95578063eff0f59214610ac8578063f8f43ff614610af857600080fd5b8063d6ae3cd5116100bb578063d6ae3cd514610a0f578063d8cc1a3c14610a42578063dabd396d14610a6257600080fd5b8063c6f0308c14610937578063cf09e0d0146109c1578063d5d44d80146109e257600080fd5b80638d450a9511610143578063bcef3b551161011d578063bcef3b55146108b7578063bd8da956146108f7578063c395e1ca1461091757600080fd5b80638d450a9514610777578063a445ece6146107aa578063bbdc02db1461087657600080fd5b80638129fc1c116101745780638129fc1c1461071a5780638980e0cc146107225780638b85902b1461073757600080fd5b806370872aa5146106f25780637b0f0adc1461070757600080fd5b80633fc8cef3116102485780635c0cba33116101fc5780636361506d116101d65780636361506d1461066c5780636b6716c0146106ac5780636f034409146106df57600080fd5b80635c0cba3314610604578063609d33341461063757806360e274641461064c57600080fd5b806354fd4d501161022d57806354fd4d501461055e57806357da950e146105b45780635a5fa2d9146105e457600080fd5b80633fc8cef314610518578063472777c61461054b57600080fd5b80632810e1d6116102aa57806337b1b2291161028457806337b1b229146104655780633a768463146104a55780633e3ac912146104d857600080fd5b80632810e1d6146103de5780632ad69aeb146103f357806330dbe5701461041357600080fd5b806319effeb4116102db57806319effeb414610339578063200d2ed21461038457806325fc2ace146103bf57600080fd5b806301935130146102f757806303c2924d14610319575b600080fd5b34801561030357600080fd5b5061031761031236600461532d565b610b9f565b005b34801561032557600080fd5b50610317610334366004615388565b610ec0565b34801561034557600080fd5b506000546103669068010000000000000000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020015b60405180910390f35b34801561039057600080fd5b506000546103b290700100000000000000000000000000000000900460ff1681565b60405161037b91906153d9565b3480156103cb57600080fd5b506008545b60405190815260200161037b565b3480156103ea57600080fd5b506103b2611566565b3480156103ff57600080fd5b506103d061040e366004615388565b61180b565b34801561041f57600080fd5b506001546104409073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161037b565b34801561047157600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90033560601c610440565b3480156104b157600080fd5b507f0000000000000000000000001c0e3b8e58dd91536caf37a6009536255a7816a6610440565b3480156104e457600080fd5b50600054610508907201000000000000000000000000000000000000900460ff1681565b604051901515815260200161037b565b34801561052457600080fd5b507f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6610440565b61031761055936600461541a565b611841565b34801561056a57600080fd5b506105a76040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b60405161037b91906154b1565b3480156105c057600080fd5b506008546009546105cf919082565b6040805192835260208301919091520161037b565b3480156105f057600080fd5b506103d06105ff3660046154c4565b611853565b34801561061057600080fd5b507f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e4610440565b34801561064357600080fd5b506105a761188d565b34801561065857600080fd5b50610317610667366004615502565b61189b565b34801561067857600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003603401356103d0565b3480156106b857600080fd5b507f0000000000000000000000000000000000000000000000000000000000000000610366565b6103176106ed366004615534565b611a42565b3480156106fe57600080fd5b506009546103d0565b61031761071536600461541a565b6123e3565b6103176123f0565b34801561072e57600080fd5b506002546103d0565b34801561074357600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401356103d0565b34801561078357600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103d0565b3480156107b657600080fd5b506108226107c53660046154c4565b6007602052600090815260409020805460019091015460ff821691610100810463ffffffff1691650100000000009091046fffffffffffffffffffffffffffffffff169073ffffffffffffffffffffffffffffffffffffffff1684565b60408051941515855263ffffffff90931660208501526fffffffffffffffffffffffffffffffff9091169183019190915273ffffffffffffffffffffffffffffffffffffffff16606082015260800161037b565b34801561088257600080fd5b5060405163ffffffff7f000000000000000000000000000000000000000000000000000000000000000016815260200161037b565b3480156108c357600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003601401356103d0565b34801561090357600080fd5b506103666109123660046154c4565b612949565b34801561092357600080fd5b506103d0610932366004615573565b612b28565b34801561094357600080fd5b506109576109523660046154c4565b612d0b565b6040805163ffffffff909816885273ffffffffffffffffffffffffffffffffffffffff968716602089015295909416948601949094526fffffffffffffffffffffffffffffffff9182166060860152608085015291821660a08401521660c082015260e00161037b565b3480156109cd57600080fd5b506000546103669067ffffffffffffffff1681565b3480156109ee57600080fd5b506103d06109fd366004615502565b60036020526000908152604090205481565b348015610a1b57600080fd5b507f00000000000000000000000000000000000000000000000000000000000003856103d0565b348015610a4e57600080fd5b50610317610a5d3660046155a5565b612da2565b348015610a6e57600080fd5b507f00000000000000000000000000000000000000000000000000000000000004b0610366565b348015610aa157600080fd5b507f00000000000000000000000000000000000000000000000000000000000000046103d0565b348015610ad457600080fd5b50610508610ae33660046154c4565b60046020526000908152604090205460ff1681565b348015610b0457600080fd5b50610317610b1336600461541a565b6133d1565b348015610b2457600080fd5b50610b2d613823565b60405161037b9392919061562f565b348015610b4857600080fd5b507f00000000000000000000000000000000000000000000000000000000000000086103d0565b348015610b7b57600080fd5b50610508610b8a3660046154c4565b60066020526000908152604090205460ff1681565b60008054700100000000000000000000000000000000900460ff166002811115610bcb57610bcb6153aa565b14610c02576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff1615610c55576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c8c367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013590565b90565b610ca3610c9e36869003860186615683565b613883565b14610cda576040517f9cc00b5b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82606001358282604051610cef929190615710565b604051809103902014610d2e576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610d77610d7284848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506138df92505050565b61394c565b90506000610d9e82600881518110610d9157610d91615720565b6020026020010151613b02565b9050602081511115610ddc576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602081810151825190910360031b1c367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401358103610e51576040517fb8ed883000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050600180547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790555050600080547fffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffff1672010000000000000000000000000000000000001790555050565b60008054700100000000000000000000000000000000900460ff166002811115610eec57610eec6153aa565b14610f23576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028381548110610f3857610f38615720565b906000526020600020906005020190506000610f5384612949565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b081169082161015610fbc576040517ff2440b5300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008481526006602052604090205460ff1615611005576040517ff1a9458100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600084815260056020526040902080548015801561102257508515155b156110bd578354640100000000900473ffffffffffffffffffffffffffffffffffffffff16600081156110555781611071565b600186015473ffffffffffffffffffffffffffffffffffffffff165b905061107d8187613bb6565b50505060009485525050600660205250506040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000868152600760209081526040918290208251608081018452815460ff81161515808352610100820463ffffffff16948301949094526501000000000090046fffffffffffffffffffffffffffffffff16938101939093526001015473ffffffffffffffffffffffffffffffffffffffff166060830152611160576fffffffffffffffffffffffffffffffff6040820152600181526000869003611160578195505b600086826020015163ffffffff16611178919061577e565b90506000838211611189578161118b565b835b602084015190915063ffffffff165b818110156112d75760008682815481106111b6576111b6615720565b6000918252602080832090910154808352600690915260409091205490915060ff1661120e576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006002828154811061122357611223615720565b600091825260209091206005909102018054909150640100000000900473ffffffffffffffffffffffffffffffffffffffff161580156112805750600481015460408701516fffffffffffffffffffffffffffffffff9182169116115b156112c257600181015473ffffffffffffffffffffffffffffffffffffffff16606087015260048101546fffffffffffffffffffffffffffffffff1660408701525b505080806112cf90615796565b91505061119a565b5063ffffffff818116602085810191825260008c81526007909152604090819020865181549351928801517fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000009094169015157fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ff161761010092909416918202939093177fffffffffffffffffffffff00000000000000000000000000000000ffffffffff16650100000000006fffffffffffffffffffffffffffffffff909316929092029190911782556060850151600190920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9093169290921790915584900361155b57606083015160008a815260066020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558915801561145757506000547201000000000000000000000000000000000000900460ff165b156114cc5760015473ffffffffffffffffffffffffffffffffffffffff1661147f818a613bb6565b885473ffffffffffffffffffffffffffffffffffffffff909116640100000000027fffffffffffffffff0000000000000000000000000000000000000000ffffffff909116178855611559565b61151373ffffffffffffffffffffffffffffffffffffffff8216156114f1578161150d565b600189015473ffffffffffffffffffffffffffffffffffffffff165b89613bb6565b87547fffffffffffffffff0000000000000000000000000000000000000000ffffffff1664010000000073ffffffffffffffffffffffffffffffffffffffff8316021788555b505b505050505050505050565b600080600054700100000000000000000000000000000000900460ff166002811115611594576115946153aa565b146115cb576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805260066020527f54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f85460ff1661162f576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008154811061165b5761165b615720565b6000918252602090912060059091020154640100000000900473ffffffffffffffffffffffffffffffffffffffff1614611696576001611699565b60025b6000805467ffffffffffffffff421668010000000000000000027fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff82168117835592935083927fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffff000000000000000000ffffffffffffffff9091161770010000000000000000000000000000000083600281111561174a5761174a6153aa565b02179055600281111561175f5761175f6153aa565b6040517f5e186f09b9c93491f14e277eea7faa5de6a2d4bda75a79af7a3684fbfb42da6090600090a27f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e473ffffffffffffffffffffffffffffffffffffffff1663838c2d1e6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156117f057600080fd5b505af1158015611804573d6000803e3d6000fd5b5050505090565b6005602052816000526040600020818154811061182757600080fd5b90600052602060002001600091509150505481565b905090565b61184e8383836001611a42565b505050565b6000818152600760209081526040808320600590925282208054825461188490610100900463ffffffff16826157ce565b95945050505050565b606061183c60546020613cb7565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260036020526040812080549082905590819003611900576040517f17bfe5f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390527f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6169063f3fef3a390604401600060405180830381600087803b15801561199057600080fd5b505af11580156119a4573d6000803e3d6000fd5b5050505060008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114611a02576040519150601f19603f3d011682016040523d82523d6000602084013e611a07565b606091505b505090508061184e576040517f83e6cc6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008054700100000000000000000000000000000000900460ff166002811115611a6e57611a6e6153aa565b14611aa5576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028481548110611aba57611aba615720565b60009182526020918290206040805160e0810182526005909302909101805463ffffffff8116845273ffffffffffffffffffffffffffffffffffffffff64010000000090910481169484019490945260018101549093169082015260028201546fffffffffffffffffffffffffffffffff908116606083015260038301546080830181905260049093015480821660a084015270010000000000000000000000000000000090041660c082015291508514611ba1576040517f3014033200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60a0810151600083156fffffffffffffffffffffffffffffffff83161760011b90506000611c61826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050861580611c9c5750611c997f0000000000000000000000000000000000000000000000000000000000000004600261577e565b81145b8015611ca6575084155b15611cdd576040517fa42637bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff168015611d03575086155b15611d3a576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000008811115611d94576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611dbf7f0000000000000000000000000000000000000000000000000000000000000004600161577e565b8103611dd157611dd186888588613d09565b34611ddb83612b28565b14611e12576040517f8620aa1900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611e1d88612949565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b0811690821603611e85576040517f3381d11400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001667ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b016611ee591906157e5565b67ffffffffffffffff16611f008267ffffffffffffffff1690565b67ffffffffffffffff161115611fe2576000611f3d60017f00000000000000000000000000000000000000000000000000000000000000046157ce565b8314611f735767ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016611fa8565b611fa87f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff16600261580e565b9050611fde817f00000000000000000000000000000000000000000000000000000000000004b067ffffffffffffffff166157e5565b9150505b6000604082901b42176000898152608086901b6fffffffffffffffffffffffffffffffff8c1617602052604081209192509060008181526004602052604090205490915060ff1615612060576040517f80497e3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60016004600083815260200190815260200160002060006101000a81548160ff02191690831515021790555060026040518060e001604052808c63ffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020013373ffffffffffffffffffffffffffffffffffffffff168152602001346fffffffffffffffffffffffffffffffff1681526020018b8152602001876fffffffffffffffffffffffffffffffff168152602001846fffffffffffffffffffffffffffffffff16815250908060018154018082558091505060019003906000526020600020906005020160009091909190915060008201518160000160006101000a81548163ffffffff021916908363ffffffff16021790555060208201518160000160046101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060608201518160020160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506080820151816003015560a08201518160040160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060c08201518160040160106101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505050600560008b815260200190815260200160002060016002805490506122f691906157ce565b81546001810183556000928352602083200155604080517fd0e30db0000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6169263d0e30db09234926004808301939282900301818588803b15801561238e57600080fd5b505af11580156123a2573d6000803e3d6000fd5b50506040513393508c92508d91507f9b3245740ec3b155098a55be84957a4da13eaf7f14a8bc6f53126c0b9350f2be90600090a45050505050505050505050565b61184e8383836000611a42565b60005471010000000000000000000000000000000000900460ff1615612442576040517f0dc149f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f7258a80700000000000000000000000000000000000000000000000000000000815263ffffffff7f0000000000000000000000000000000000000000000000000000000000000000166004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e41690637258a807906024016040805180830381865afa1580156124f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061251a919061583e565b909250905081612556576040517f6a6bc3b200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080518082019091528281526020018190526008829055600981905536607a1461258957639824bdab6000526004601cfd5b80367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036054013511612623576040517ff40239db000000000000000000000000000000000000000000000000000000008152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013560048201526024015b60405180910390fd5b6040805160e08101825263ffffffff8082526000602083018181527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe369081013560f01c90038035606090811c868801908152346fffffffffffffffffffffffffffffffff81811693890193845260149094013560808901908152600160a08a0181815242871660c08c019081526002805493840181558a529a5160059092027f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace81018054995173ffffffffffffffffffffffffffffffffffffffff908116640100000000027fffffffffffffffff000000000000000000000000000000000000000000000000909b1694909c16939093179890981790915592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf87018054918a167fffffffffffffffffffffffff000000000000000000000000000000000000000090921691909117905592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad0860180549186167fffffffffffffffffffffffffffffffff0000000000000000000000000000000090921691909117905591517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad185015551955182167001000000000000000000000000000000000295909116949094177f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad29091015580547fffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffff167101000000000000000000000000000000000017815583517fd0e30db000000000000000000000000000000000000000000000000000000000815293517f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa69092169363d0e30db093926004828101939282900301818588803b1580156128f857600080fd5b505af115801561290c573d6000803e3d6000fd5b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000164267ffffffffffffffff161790555050505050565b600080600054700100000000000000000000000000000000900460ff166002811115612977576129776153aa565b146129ae576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600283815481106129c3576129c3615720565b600091825260208220600590910201805490925063ffffffff90811614612a3257815460028054909163ffffffff16908110612a0157612a01615720565b906000526020600020906005020160040160109054906101000a90046fffffffffffffffffffffffffffffffff1690505b6004820154600090612a6a90700100000000000000000000000000000000900467ffffffffffffffff165b67ffffffffffffffff1690565b612a7e9067ffffffffffffffff16426157ce565b612a9d612a5d846fffffffffffffffffffffffffffffffff1660401c90565b67ffffffffffffffff16612ab1919061577e565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b01667ffffffffffffffff168167ffffffffffffffff1611612afe5780611884565b7f00000000000000000000000000000000000000000000000000000000000004b095945050505050565b600080612bc7836fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690507f0000000000000000000000000000000000000000000000000000000000000008811115612c26576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b642e90edd00062061a806311e1a3006000612c418383615891565b9050670de0b6b3a76400006000612c78827f00000000000000000000000000000000000000000000000000000000000000086158a5565b90506000612c96612c91670de0b6b3a7640000866158a5565b613eba565b90506000612ca48484614115565b90506000612cb28383614164565b90506000612cbf82614192565b90506000612cde82612cd9670de0b6b3a76400008f6158a5565b61437a565b90506000612cec8b83614164565b9050612cf8818d6158a5565b9f9e505050505050505050505050505050565b60028181548110612d1b57600080fd5b60009182526020909120600590910201805460018201546002830154600384015460049094015463ffffffff8416955064010000000090930473ffffffffffffffffffffffffffffffffffffffff908116949216926fffffffffffffffffffffffffffffffff91821692918082169170010000000000000000000000000000000090041687565b60008054700100000000000000000000000000000000900460ff166002811115612dce57612dce6153aa565b14612e05576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028781548110612e1a57612e1a615720565b6000918252602082206005919091020160048101549092506fffffffffffffffffffffffffffffffff16908715821760011b9050612e797f0000000000000000000000000000000000000000000000000000000000000008600161577e565b612f15826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1614612f4f576040517f5f53dd9800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080891561304657612fa27f00000000000000000000000000000000000000000000000000000000000000047f00000000000000000000000000000000000000000000000000000000000000086157ce565b6001901b612fc1846fffffffffffffffffffffffffffffffff166143b4565b6fffffffffffffffffffffffffffffffff16612fdd91906158e2565b1561301a5761301161300260016fffffffffffffffffffffffffffffffff87166158f6565b865463ffffffff166000614453565b6003015461303c565b7f00000000000000000000000000000000000000000000000000000000000000005b9150849050613070565b6003850154915061306d6130026fffffffffffffffffffffffffffffffff8616600161591f565b90505b600882901b60088a8a604051613087929190615710565b6040518091039020901b146130c8576040517f696550ff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006130d38c614537565b905060006130e2836003015490565b6040517fe14ced320000000000000000000000000000000000000000000000000000000081527f0000000000000000000000001c0e3b8e58dd91536caf37a6009536255a7816a673ffffffffffffffffffffffffffffffffffffffff169063e14ced329061315c908f908f908f908f908a9060040161599c565b6020604051808303816000875af115801561317b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061319f91906159d6565b60048501549114915060009060029061324a906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b6132e6896fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b6132f091906159ef565b6132fa9190615a12565b60ff16159050811515810361333b576040517ffb4e40dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8754640100000000900473ffffffffffffffffffffffffffffffffffffffff1615613392576040517f9071e6af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505085547fffffffffffffffff0000000000000000000000000000000000000000ffffffff163364010000000002179095555050505050505050505050565b60008054700100000000000000000000000000000000900460ff1660028111156133fd576133fd6153aa565b14613434576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060008061344386614566565b935093509350935060006134598585858561496f565b905060007f0000000000000000000000001c0e3b8e58dd91536caf37a6009536255a7816a673ffffffffffffffffffffffffffffffffffffffff16637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156134c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134ec9190615a34565b9050600189036135e45773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a84613548367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036034013590565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b16815260048101939093526024830191909152604482015260206064820152608481018a905260a4015b6020604051808303816000875af11580156135ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135de91906159d6565b5061155b565b600289036136105773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a8489613548565b6003890361363c5773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a8487613548565b600489036137585760006136826fffffffffffffffffffffffffffffffff85167f0000000000000000000000000000000000000000000000000000000000000004614a29565b60095461368f919061577e565b61369a90600161577e565b905073ffffffffffffffffffffffffffffffffffffffff82166352f0f3ad8b8560405160e084901b7fffffffff000000000000000000000000000000000000000000000000000000001681526004810192909252602482015260c084901b604482015260086064820152608481018b905260a4016020604051808303816000875af115801561372d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061375191906159d6565b505061155b565b600589036137f1576040517f52f0f3ad000000000000000000000000000000000000000000000000000000008152600481018a9052602481018390527f000000000000000000000000000000000000000000000000000000000000038560c01b6044820152600860648201526084810188905273ffffffffffffffffffffffffffffffffffffffff8216906352f0f3ad9060a40161359b565b6040517fff137e6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c900360140135606061387c61188d565b9050909192565b600081600001518260200151836040015184606001516040516020016138c2949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b6040805180820190915260008082526020820152815160000361392e576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b6060600080600061395c85614ad7565b919450925090506001816001811115613977576139776153aa565b146139ae576040517f4b9c6abe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84516139ba838561577e565b146139f1576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516020808252610420820190925290816020015b6040805180820190915260008082526020820152815260200190600190039081613a085790505093506000835b8651811015613af657600080613a7b6040518060400160405280858c60000151613a5f91906157ce565b8152602001858c60200151613a74919061577e565b9052614ad7565b509150915060405180604001604052808383613a97919061577e565b8152602001848b60200151613aac919061577e565b815250888581518110613ac157613ac1615720565b6020908102919091010152613ad760018561577e565b9350613ae3818361577e565b613aed908461577e565b92505050613a35565b50845250919392505050565b60606000806000613b1285614ad7565b919450925090506000816001811115613b2d57613b2d6153aa565b14613b64576040517f1ff9b2e400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613b6e828461577e565b855114613ba7576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61188485602001518484614f75565b600281015473ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080546fffffffffffffffffffffffffffffffff90931692839290613c0590849061577e565b90915550506040517f7eee288d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152602482018390527f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa61690637eee288d90604401600060405180830381600087803b158015613c9a57600080fd5b505af1158015613cae573d6000803e3d6000fd5b50505050505050565b604051818152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90038284820160208401378260208301016000815260208101604052505092915050565b6000613d286fffffffffffffffffffffffffffffffff8416600161591f565b90506000613d3882866001614453565b9050600086901a8380613e245750613d7160027f00000000000000000000000000000000000000000000000000000000000000046158e2565b6004830154600290613e15906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b613e1f9190615a12565b60ff16145b15613e7c5760ff811660011480613e3e575060ff81166002145b613e77576040517ff40239db0000000000000000000000000000000000000000000000000000000081526004810188905260240161261a565b613cae565b60ff811615613cae576040517ff40239db0000000000000000000000000000000000000000000000000000000081526004810188905260240161261a565b6fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff1060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b1760008213613f1957631615e6386000526004601cfd5b7ff8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff6f8421084210842108cc6318c6db6d54be83831c1c601f161a1890811b609f90811c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506029190037d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b302017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d90565b60007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218311670de0b6b3a76400000215820261415257637c5f487d6000526004601cfd5b50670de0b6b3a7640000919091020490565b6000816000190483118202156141825763bac65e5b6000526004601cfd5b50670de0b6b3a764000091020490565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdc0d0570925a462d782136141c057919050565b680755bf798b4a1bf1e582126141de5763a37bfec96000526004601cfd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b60006143ab670de0b6b3a76400008361439286613eba565b61439c9190615a51565b6143a69190615b0d565b614192565b90505b92915050565b600080614441837e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b600160ff919091161b90920392915050565b6000808261449c576144976fffffffffffffffffffffffffffffffff86167f000000000000000000000000000000000000000000000000000000000000000461500a565b6144b7565b6144b7856fffffffffffffffffffffffffffffffff16615196565b9050600284815481106144cc576144cc615720565b906000526020600020906005020191505b60048201546fffffffffffffffffffffffffffffffff82811691161461452f57815460028054909163ffffffff1690811061451a5761451a615720565b906000526020600020906005020191506144dd565b509392505050565b600080600080600061454886614566565b935093509350935061455c8484848461496f565b9695505050505050565b600080600080600085905060006002828154811061458657614586615720565b600091825260209091206004600590920201908101549091507f00000000000000000000000000000000000000000000000000000000000000049061465d906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1611614697576040517fb34b5c2200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000815b60048301547f00000000000000000000000000000000000000000000000000000000000000049061475e906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1692508211156147d357825463ffffffff1661479d7f0000000000000000000000000000000000000000000000000000000000000004600161577e565b83036147a7578391505b600281815481106147ba576147ba615720565b906000526020600020906005020193508094505061469b565b600481810154908401546fffffffffffffffffffffffffffffffff91821691166000816fffffffffffffffffffffffffffffffff1661483c614827856fffffffffffffffffffffffffffffffff1660011c90565b6fffffffffffffffffffffffffffffffff1690565b6fffffffffffffffffffffffffffffffff16149050801561490b576000614874836fffffffffffffffffffffffffffffffff166143b4565b6fffffffffffffffffffffffffffffffff1611156148df5760006148b66148ae60016fffffffffffffffffffffffffffffffff86166158f6565b896001614453565b6003810154600490910154909c506fffffffffffffffffffffffffffffffff169a506148e59050565b6008549a505b600386015460048701549099506fffffffffffffffffffffffffffffffff169750614961565b600061492d6148ae6fffffffffffffffffffffffffffffffff8516600161591f565b6003808901546004808b015492840154930154909e506fffffffffffffffffffffffffffffffff9182169d50919b50169850505b505050505050509193509193565b60006fffffffffffffffffffffffffffffffff8416156149dc5760408051602081018790526fffffffffffffffffffffffffffffffff8087169282019290925260608101859052908316608082015260a00160405160208183030381529060405280519060200120611884565b8282604051602001614a0a9291909182526fffffffffffffffffffffffffffffffff16602082015260400190565b6040516020818303038152906040528051906020012095945050505050565b600080614ab6847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690508083036001841b600180831b0386831b17039250505092915050565b60008060008360000151600003614b1a576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020840151805160001a607f8111614b3f576000600160009450945094505050614f6e565b60b78111614c55576000614b546080836157ce565b905080876000015111614b93576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001838101517fff00000000000000000000000000000000000000000000000000000000000000169082148015614c0b57507f80000000000000000000000000000000000000000000000000000000000000007fff000000000000000000000000000000000000000000000000000000000000008216105b15614c42576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060019550935060009250614f6e915050565b60bf8111614db3576000614c6a60b7836157ce565b905080876000015111614ca9576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614d0b576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614d53576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614d5d818461577e565b895111614d96576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614da183600161577e565b9750955060009450614f6e9350505050565b60f78111614e18576000614dc860c0836157ce565b905080876000015111614e07576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600195509350849250614f6e915050565b6000614e2560f7836157ce565b905080876000015111614e64576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614ec6576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614f0e576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614f18818461577e565b895111614f51576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614f5c83600161577e565b9750955060019450614f6e9350505050565b9193909250565b60608167ffffffffffffffff811115614f9057614f90615654565b6040519080825280601f01601f191660200182016040528015614fba576020820181803683370190505b5090508115615003576000614fcf848661577e565b90506020820160005b84811015614ff0578281015182820152602001614fd8565b84811115614fff576000858301525b5050505b9392505050565b6000816150a9846fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff16116150bf5763b34b5c226000526004601cfd5b6150c883615196565b905081615167826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff16116143ae576143ab61517d83600161577e565b6fffffffffffffffffffffffffffffffff83169061523b565b6000811960018301168161522a827e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169390931c8015179392505050565b6000806152c8847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050808303600180821b0385821b179250505092915050565b60008083601f8401126152f657600080fd5b50813567ffffffffffffffff81111561530e57600080fd5b60208301915083602082850101111561532657600080fd5b9250929050565b600080600083850360a081121561534357600080fd5b608081121561535157600080fd5b50839250608084013567ffffffffffffffff81111561536f57600080fd5b61537b868287016152e4565b9497909650939450505050565b6000806040838503121561539b57600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6020810160038310615414577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b60008060006060848603121561542f57600080fd5b505081359360208301359350604090920135919050565b6000815180845260005b8181101561546c57602081850181015186830182015201615450565b8181111561547e576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006143ab6020830184615446565b6000602082840312156154d657600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff811681146154ff57600080fd5b50565b60006020828403121561551457600080fd5b8135615003816154dd565b8035801515811461552f57600080fd5b919050565b6000806000806080858703121561554a57600080fd5b8435935060208501359250604085013591506155686060860161551f565b905092959194509250565b60006020828403121561558557600080fd5b81356fffffffffffffffffffffffffffffffff8116811461500357600080fd5b600080600080600080608087890312156155be57600080fd5b863595506155ce6020880161551f565b9450604087013567ffffffffffffffff808211156155eb57600080fd5b6155f78a838b016152e4565b9096509450606089013591508082111561561057600080fd5b5061561d89828a016152e4565b979a9699509497509295939492505050565b63ffffffff841681528260208201526060604082015260006118846060830184615446565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006080828403121561569557600080fd5b6040516080810181811067ffffffffffffffff821117156156df577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b8183823760009101908152919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082198211156157915761579161574f565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036157c7576157c761574f565b5060010190565b6000828210156157e0576157e061574f565b500390565b600067ffffffffffffffff838116908316818110156158065761580661574f565b039392505050565b600067ffffffffffffffff808316818516818304811182151516156158355761583561574f565b02949350505050565b6000806040838503121561585157600080fd5b505080516020909101519092909150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000826158a0576158a0615862565b500490565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156158dd576158dd61574f565b500290565b6000826158f1576158f1615862565b500690565b60006fffffffffffffffffffffffffffffffff838116908316818110156158065761580661574f565b60006fffffffffffffffffffffffffffffffff80831681851680830382111561594a5761594a61574f565b01949350505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b6060815260006159b0606083018789615953565b82810360208401526159c3818688615953565b9150508260408301529695505050505050565b6000602082840312156159e857600080fd5b5051919050565b600060ff821660ff841680821015615a0957615a0961574f565b90039392505050565b600060ff831680615a2557615a25615862565b8060ff84160691505092915050565b600060208284031215615a4657600080fd5b8151615003816154dd565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615615a9257615a9261574f565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615615acd57615acd61574f565b60008712925087820587128484161615615ae957615ae961574f565b87850587128184161615615aff57615aff61574f565b505050929093029392505050565b600082615b1c57615b1c615862565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615615b7057615b7061574f565b50059056fea164736f6c634300080f000a"; + hex"6080604052600436106102f25760003560e01c806370872aa51161018f578063c6f0308c116100e1578063ec5e63081161008a578063fa24f74311610064578063fa24f74314610b18578063fa315aa914610b3c578063fe2bbeb214610b6f57600080fd5b8063ec5e630814610a95578063eff0f59214610ac8578063f8f43ff614610af857600080fd5b8063d6ae3cd5116100bb578063d6ae3cd514610a0f578063d8cc1a3c14610a42578063dabd396d14610a6257600080fd5b8063c6f0308c14610937578063cf09e0d0146109c1578063d5d44d80146109e257600080fd5b80638d450a9511610143578063bcef3b551161011d578063bcef3b55146108b7578063bd8da956146108f7578063c395e1ca1461091757600080fd5b80638d450a9514610777578063a445ece6146107aa578063bbdc02db1461087657600080fd5b80638129fc1c116101745780638129fc1c1461071a5780638980e0cc146107225780638b85902b1461073757600080fd5b806370872aa5146106f25780637b0f0adc1461070757600080fd5b80633fc8cef3116102485780635c0cba33116101fc5780636361506d116101d65780636361506d1461066c5780636b6716c0146106ac5780636f034409146106df57600080fd5b80635c0cba3314610604578063609d33341461063757806360e274641461064c57600080fd5b806354fd4d501161022d57806354fd4d501461055e57806357da950e146105b45780635a5fa2d9146105e457600080fd5b80633fc8cef314610518578063472777c61461054b57600080fd5b80632810e1d6116102aa57806337b1b2291161028457806337b1b229146104655780633a768463146104a55780633e3ac912146104d857600080fd5b80632810e1d6146103de5780632ad69aeb146103f357806330dbe5701461041357600080fd5b806319effeb4116102db57806319effeb414610339578063200d2ed21461038457806325fc2ace146103bf57600080fd5b806301935130146102f757806303c2924d14610319575b600080fd5b34801561030357600080fd5b5061031761031236600461532d565b610b9f565b005b34801561032557600080fd5b50610317610334366004615388565b610ec0565b34801561034557600080fd5b506000546103669068010000000000000000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020015b60405180910390f35b34801561039057600080fd5b506000546103b290700100000000000000000000000000000000900460ff1681565b60405161037b91906153d9565b3480156103cb57600080fd5b506008545b60405190815260200161037b565b3480156103ea57600080fd5b506103b2611566565b3480156103ff57600080fd5b506103d061040e366004615388565b61180b565b34801561041f57600080fd5b506001546104409073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161037b565b34801561047157600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90033560601c610440565b3480156104b157600080fd5b507f00000000000000000000000028bf1582225713139c0e898326db808b6484cfd4610440565b3480156104e457600080fd5b50600054610508907201000000000000000000000000000000000000900460ff1681565b604051901515815260200161037b565b34801561052457600080fd5b507f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6610440565b61031761055936600461541a565b611841565b34801561056a57600080fd5b506105a76040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b60405161037b91906154b1565b3480156105c057600080fd5b506008546009546105cf919082565b6040805192835260208301919091520161037b565b3480156105f057600080fd5b506103d06105ff3660046154c4565b611853565b34801561061057600080fd5b507f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e4610440565b34801561064357600080fd5b506105a761188d565b34801561065857600080fd5b50610317610667366004615502565b61189b565b34801561067857600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003603401356103d0565b3480156106b857600080fd5b507f0000000000000000000000000000000000000000000000000000000000000000610366565b6103176106ed366004615534565b611a42565b3480156106fe57600080fd5b506009546103d0565b61031761071536600461541a565b6123e3565b6103176123f0565b34801561072e57600080fd5b506002546103d0565b34801561074357600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401356103d0565b34801561078357600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103d0565b3480156107b657600080fd5b506108226107c53660046154c4565b6007602052600090815260409020805460019091015460ff821691610100810463ffffffff1691650100000000009091046fffffffffffffffffffffffffffffffff169073ffffffffffffffffffffffffffffffffffffffff1684565b60408051941515855263ffffffff90931660208501526fffffffffffffffffffffffffffffffff9091169183019190915273ffffffffffffffffffffffffffffffffffffffff16606082015260800161037b565b34801561088257600080fd5b5060405163ffffffff7f000000000000000000000000000000000000000000000000000000000000000016815260200161037b565b3480156108c357600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003601401356103d0565b34801561090357600080fd5b506103666109123660046154c4565b612949565b34801561092357600080fd5b506103d0610932366004615573565b612b28565b34801561094357600080fd5b506109576109523660046154c4565b612d0b565b6040805163ffffffff909816885273ffffffffffffffffffffffffffffffffffffffff968716602089015295909416948601949094526fffffffffffffffffffffffffffffffff9182166060860152608085015291821660a08401521660c082015260e00161037b565b3480156109cd57600080fd5b506000546103669067ffffffffffffffff1681565b3480156109ee57600080fd5b506103d06109fd366004615502565b60036020526000908152604090205481565b348015610a1b57600080fd5b507f00000000000000000000000000000000000000000000000000000000000003856103d0565b348015610a4e57600080fd5b50610317610a5d3660046155a5565b612da2565b348015610a6e57600080fd5b507f00000000000000000000000000000000000000000000000000000000000004b0610366565b348015610aa157600080fd5b507f00000000000000000000000000000000000000000000000000000000000000046103d0565b348015610ad457600080fd5b50610508610ae33660046154c4565b60046020526000908152604090205460ff1681565b348015610b0457600080fd5b50610317610b1336600461541a565b6133d1565b348015610b2457600080fd5b50610b2d613823565b60405161037b9392919061562f565b348015610b4857600080fd5b507f00000000000000000000000000000000000000000000000000000000000000086103d0565b348015610b7b57600080fd5b50610508610b8a3660046154c4565b60066020526000908152604090205460ff1681565b60008054700100000000000000000000000000000000900460ff166002811115610bcb57610bcb6153aa565b14610c02576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff1615610c55576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c8c367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013590565b90565b610ca3610c9e36869003860186615683565b613883565b14610cda576040517f9cc00b5b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82606001358282604051610cef929190615710565b604051809103902014610d2e576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610d77610d7284848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506138df92505050565b61394c565b90506000610d9e82600881518110610d9157610d91615720565b6020026020010151613b02565b9050602081511115610ddc576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602081810151825190910360031b1c367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401358103610e51576040517fb8ed883000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050600180547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790555050600080547fffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffff1672010000000000000000000000000000000000001790555050565b60008054700100000000000000000000000000000000900460ff166002811115610eec57610eec6153aa565b14610f23576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028381548110610f3857610f38615720565b906000526020600020906005020190506000610f5384612949565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b081169082161015610fbc576040517ff2440b5300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008481526006602052604090205460ff1615611005576040517ff1a9458100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600084815260056020526040902080548015801561102257508515155b156110bd578354640100000000900473ffffffffffffffffffffffffffffffffffffffff16600081156110555781611071565b600186015473ffffffffffffffffffffffffffffffffffffffff165b905061107d8187613bb6565b50505060009485525050600660205250506040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000868152600760209081526040918290208251608081018452815460ff81161515808352610100820463ffffffff16948301949094526501000000000090046fffffffffffffffffffffffffffffffff16938101939093526001015473ffffffffffffffffffffffffffffffffffffffff166060830152611160576fffffffffffffffffffffffffffffffff6040820152600181526000869003611160578195505b600086826020015163ffffffff16611178919061577e565b90506000838211611189578161118b565b835b602084015190915063ffffffff165b818110156112d75760008682815481106111b6576111b6615720565b6000918252602080832090910154808352600690915260409091205490915060ff1661120e576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006002828154811061122357611223615720565b600091825260209091206005909102018054909150640100000000900473ffffffffffffffffffffffffffffffffffffffff161580156112805750600481015460408701516fffffffffffffffffffffffffffffffff9182169116115b156112c257600181015473ffffffffffffffffffffffffffffffffffffffff16606087015260048101546fffffffffffffffffffffffffffffffff1660408701525b505080806112cf90615796565b91505061119a565b5063ffffffff818116602085810191825260008c81526007909152604090819020865181549351928801517fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000009094169015157fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ff161761010092909416918202939093177fffffffffffffffffffffff00000000000000000000000000000000ffffffffff16650100000000006fffffffffffffffffffffffffffffffff909316929092029190911782556060850151600190920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9093169290921790915584900361155b57606083015160008a815260066020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558915801561145757506000547201000000000000000000000000000000000000900460ff165b156114cc5760015473ffffffffffffffffffffffffffffffffffffffff1661147f818a613bb6565b885473ffffffffffffffffffffffffffffffffffffffff909116640100000000027fffffffffffffffff0000000000000000000000000000000000000000ffffffff909116178855611559565b61151373ffffffffffffffffffffffffffffffffffffffff8216156114f1578161150d565b600189015473ffffffffffffffffffffffffffffffffffffffff165b89613bb6565b87547fffffffffffffffff0000000000000000000000000000000000000000ffffffff1664010000000073ffffffffffffffffffffffffffffffffffffffff8316021788555b505b505050505050505050565b600080600054700100000000000000000000000000000000900460ff166002811115611594576115946153aa565b146115cb576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805260066020527f54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f85460ff1661162f576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008154811061165b5761165b615720565b6000918252602090912060059091020154640100000000900473ffffffffffffffffffffffffffffffffffffffff1614611696576001611699565b60025b6000805467ffffffffffffffff421668010000000000000000027fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff82168117835592935083927fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffff000000000000000000ffffffffffffffff9091161770010000000000000000000000000000000083600281111561174a5761174a6153aa565b02179055600281111561175f5761175f6153aa565b6040517f5e186f09b9c93491f14e277eea7faa5de6a2d4bda75a79af7a3684fbfb42da6090600090a27f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e473ffffffffffffffffffffffffffffffffffffffff1663838c2d1e6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156117f057600080fd5b505af1158015611804573d6000803e3d6000fd5b5050505090565b6005602052816000526040600020818154811061182757600080fd5b90600052602060002001600091509150505481565b905090565b61184e8383836001611a42565b505050565b6000818152600760209081526040808320600590925282208054825461188490610100900463ffffffff16826157ce565b95945050505050565b606061183c60546020613cb7565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260036020526040812080549082905590819003611900576040517f17bfe5f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390527f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6169063f3fef3a390604401600060405180830381600087803b15801561199057600080fd5b505af11580156119a4573d6000803e3d6000fd5b5050505060008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114611a02576040519150601f19603f3d011682016040523d82523d6000602084013e611a07565b606091505b505090508061184e576040517f83e6cc6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008054700100000000000000000000000000000000900460ff166002811115611a6e57611a6e6153aa565b14611aa5576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028481548110611aba57611aba615720565b60009182526020918290206040805160e0810182526005909302909101805463ffffffff8116845273ffffffffffffffffffffffffffffffffffffffff64010000000090910481169484019490945260018101549093169082015260028201546fffffffffffffffffffffffffffffffff908116606083015260038301546080830181905260049093015480821660a084015270010000000000000000000000000000000090041660c082015291508514611ba1576040517f3014033200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60a0810151600083156fffffffffffffffffffffffffffffffff83161760011b90506000611c61826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050861580611c9c5750611c997f0000000000000000000000000000000000000000000000000000000000000004600261577e565b81145b8015611ca6575084155b15611cdd576040517fa42637bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff168015611d03575086155b15611d3a576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000008811115611d94576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611dbf7f0000000000000000000000000000000000000000000000000000000000000004600161577e565b8103611dd157611dd186888588613d09565b34611ddb83612b28565b14611e12576040517f8620aa1900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611e1d88612949565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b0811690821603611e85576040517f3381d11400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001667ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b016611ee591906157e5565b67ffffffffffffffff16611f008267ffffffffffffffff1690565b67ffffffffffffffff161115611fe2576000611f3d60017f00000000000000000000000000000000000000000000000000000000000000046157ce565b8314611f735767ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016611fa8565b611fa87f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff16600261580e565b9050611fde817f00000000000000000000000000000000000000000000000000000000000004b067ffffffffffffffff166157e5565b9150505b6000604082901b42176000898152608086901b6fffffffffffffffffffffffffffffffff8c1617602052604081209192509060008181526004602052604090205490915060ff1615612060576040517f80497e3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60016004600083815260200190815260200160002060006101000a81548160ff02191690831515021790555060026040518060e001604052808c63ffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020013373ffffffffffffffffffffffffffffffffffffffff168152602001346fffffffffffffffffffffffffffffffff1681526020018b8152602001876fffffffffffffffffffffffffffffffff168152602001846fffffffffffffffffffffffffffffffff16815250908060018154018082558091505060019003906000526020600020906005020160009091909190915060008201518160000160006101000a81548163ffffffff021916908363ffffffff16021790555060208201518160000160046101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060608201518160020160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506080820151816003015560a08201518160040160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060c08201518160040160106101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505050600560008b815260200190815260200160002060016002805490506122f691906157ce565b81546001810183556000928352602083200155604080517fd0e30db0000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6169263d0e30db09234926004808301939282900301818588803b15801561238e57600080fd5b505af11580156123a2573d6000803e3d6000fd5b50506040513393508c92508d91507f9b3245740ec3b155098a55be84957a4da13eaf7f14a8bc6f53126c0b9350f2be90600090a45050505050505050505050565b61184e8383836000611a42565b60005471010000000000000000000000000000000000900460ff1615612442576040517f0dc149f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f7258a80700000000000000000000000000000000000000000000000000000000815263ffffffff7f0000000000000000000000000000000000000000000000000000000000000000166004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e41690637258a807906024016040805180830381865afa1580156124f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061251a919061583e565b909250905081612556576040517f6a6bc3b200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080518082019091528281526020018190526008829055600981905536607a1461258957639824bdab6000526004601cfd5b80367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036054013511612623576040517ff40239db000000000000000000000000000000000000000000000000000000008152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013560048201526024015b60405180910390fd5b6040805160e08101825263ffffffff8082526000602083018181527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe369081013560f01c90038035606090811c868801908152346fffffffffffffffffffffffffffffffff81811693890193845260149094013560808901908152600160a08a0181815242871660c08c019081526002805493840181558a529a5160059092027f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace81018054995173ffffffffffffffffffffffffffffffffffffffff908116640100000000027fffffffffffffffff000000000000000000000000000000000000000000000000909b1694909c16939093179890981790915592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf87018054918a167fffffffffffffffffffffffff000000000000000000000000000000000000000090921691909117905592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad0860180549186167fffffffffffffffffffffffffffffffff0000000000000000000000000000000090921691909117905591517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad185015551955182167001000000000000000000000000000000000295909116949094177f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad29091015580547fffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffff167101000000000000000000000000000000000017815583517fd0e30db000000000000000000000000000000000000000000000000000000000815293517f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa69092169363d0e30db093926004828101939282900301818588803b1580156128f857600080fd5b505af115801561290c573d6000803e3d6000fd5b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000164267ffffffffffffffff161790555050505050565b600080600054700100000000000000000000000000000000900460ff166002811115612977576129776153aa565b146129ae576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600283815481106129c3576129c3615720565b600091825260208220600590910201805490925063ffffffff90811614612a3257815460028054909163ffffffff16908110612a0157612a01615720565b906000526020600020906005020160040160109054906101000a90046fffffffffffffffffffffffffffffffff1690505b6004820154600090612a6a90700100000000000000000000000000000000900467ffffffffffffffff165b67ffffffffffffffff1690565b612a7e9067ffffffffffffffff16426157ce565b612a9d612a5d846fffffffffffffffffffffffffffffffff1660401c90565b67ffffffffffffffff16612ab1919061577e565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b01667ffffffffffffffff168167ffffffffffffffff1611612afe5780611884565b7f00000000000000000000000000000000000000000000000000000000000004b095945050505050565b600080612bc7836fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690507f0000000000000000000000000000000000000000000000000000000000000008811115612c26576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b642e90edd00062061a806311e1a3006000612c418383615891565b9050670de0b6b3a76400006000612c78827f00000000000000000000000000000000000000000000000000000000000000086158a5565b90506000612c96612c91670de0b6b3a7640000866158a5565b613eba565b90506000612ca48484614115565b90506000612cb28383614164565b90506000612cbf82614192565b90506000612cde82612cd9670de0b6b3a76400008f6158a5565b61437a565b90506000612cec8b83614164565b9050612cf8818d6158a5565b9f9e505050505050505050505050505050565b60028181548110612d1b57600080fd5b60009182526020909120600590910201805460018201546002830154600384015460049094015463ffffffff8416955064010000000090930473ffffffffffffffffffffffffffffffffffffffff908116949216926fffffffffffffffffffffffffffffffff91821692918082169170010000000000000000000000000000000090041687565b60008054700100000000000000000000000000000000900460ff166002811115612dce57612dce6153aa565b14612e05576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028781548110612e1a57612e1a615720565b6000918252602082206005919091020160048101549092506fffffffffffffffffffffffffffffffff16908715821760011b9050612e797f0000000000000000000000000000000000000000000000000000000000000008600161577e565b612f15826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1614612f4f576040517f5f53dd9800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080891561304657612fa27f00000000000000000000000000000000000000000000000000000000000000047f00000000000000000000000000000000000000000000000000000000000000086157ce565b6001901b612fc1846fffffffffffffffffffffffffffffffff166143b4565b6fffffffffffffffffffffffffffffffff16612fdd91906158e2565b1561301a5761301161300260016fffffffffffffffffffffffffffffffff87166158f6565b865463ffffffff166000614453565b6003015461303c565b7f00000000000000000000000000000000000000000000000000000000000000005b9150849050613070565b6003850154915061306d6130026fffffffffffffffffffffffffffffffff8616600161591f565b90505b600882901b60088a8a604051613087929190615710565b6040518091039020901b146130c8576040517f696550ff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006130d38c614537565b905060006130e2836003015490565b6040517fe14ced320000000000000000000000000000000000000000000000000000000081527f00000000000000000000000028bf1582225713139c0e898326db808b6484cfd473ffffffffffffffffffffffffffffffffffffffff169063e14ced329061315c908f908f908f908f908a9060040161599c565b6020604051808303816000875af115801561317b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061319f91906159d6565b60048501549114915060009060029061324a906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b6132e6896fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b6132f091906159ef565b6132fa9190615a12565b60ff16159050811515810361333b576040517ffb4e40dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8754640100000000900473ffffffffffffffffffffffffffffffffffffffff1615613392576040517f9071e6af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505085547fffffffffffffffff0000000000000000000000000000000000000000ffffffff163364010000000002179095555050505050505050505050565b60008054700100000000000000000000000000000000900460ff1660028111156133fd576133fd6153aa565b14613434576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060008061344386614566565b935093509350935060006134598585858561496f565b905060007f00000000000000000000000028bf1582225713139c0e898326db808b6484cfd473ffffffffffffffffffffffffffffffffffffffff16637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156134c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134ec9190615a34565b9050600189036135e45773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a84613548367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036034013590565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b16815260048101939093526024830191909152604482015260206064820152608481018a905260a4015b6020604051808303816000875af11580156135ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135de91906159d6565b5061155b565b600289036136105773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a8489613548565b6003890361363c5773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a8487613548565b600489036137585760006136826fffffffffffffffffffffffffffffffff85167f0000000000000000000000000000000000000000000000000000000000000004614a29565b60095461368f919061577e565b61369a90600161577e565b905073ffffffffffffffffffffffffffffffffffffffff82166352f0f3ad8b8560405160e084901b7fffffffff000000000000000000000000000000000000000000000000000000001681526004810192909252602482015260c084901b604482015260086064820152608481018b905260a4016020604051808303816000875af115801561372d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061375191906159d6565b505061155b565b600589036137f1576040517f52f0f3ad000000000000000000000000000000000000000000000000000000008152600481018a9052602481018390527f000000000000000000000000000000000000000000000000000000000000038560c01b6044820152600860648201526084810188905273ffffffffffffffffffffffffffffffffffffffff8216906352f0f3ad9060a40161359b565b6040517fff137e6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c900360140135606061387c61188d565b9050909192565b600081600001518260200151836040015184606001516040516020016138c2949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b6040805180820190915260008082526020820152815160000361392e576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b6060600080600061395c85614ad7565b919450925090506001816001811115613977576139776153aa565b146139ae576040517f4b9c6abe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84516139ba838561577e565b146139f1576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516020808252610420820190925290816020015b6040805180820190915260008082526020820152815260200190600190039081613a085790505093506000835b8651811015613af657600080613a7b6040518060400160405280858c60000151613a5f91906157ce565b8152602001858c60200151613a74919061577e565b9052614ad7565b509150915060405180604001604052808383613a97919061577e565b8152602001848b60200151613aac919061577e565b815250888581518110613ac157613ac1615720565b6020908102919091010152613ad760018561577e565b9350613ae3818361577e565b613aed908461577e565b92505050613a35565b50845250919392505050565b60606000806000613b1285614ad7565b919450925090506000816001811115613b2d57613b2d6153aa565b14613b64576040517f1ff9b2e400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613b6e828461577e565b855114613ba7576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61188485602001518484614f75565b600281015473ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080546fffffffffffffffffffffffffffffffff90931692839290613c0590849061577e565b90915550506040517f7eee288d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152602482018390527f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa61690637eee288d90604401600060405180830381600087803b158015613c9a57600080fd5b505af1158015613cae573d6000803e3d6000fd5b50505050505050565b604051818152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90038284820160208401378260208301016000815260208101604052505092915050565b6000613d286fffffffffffffffffffffffffffffffff8416600161591f565b90506000613d3882866001614453565b9050600086901a8380613e245750613d7160027f00000000000000000000000000000000000000000000000000000000000000046158e2565b6004830154600290613e15906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b613e1f9190615a12565b60ff16145b15613e7c5760ff811660011480613e3e575060ff81166002145b613e77576040517ff40239db0000000000000000000000000000000000000000000000000000000081526004810188905260240161261a565b613cae565b60ff811615613cae576040517ff40239db0000000000000000000000000000000000000000000000000000000081526004810188905260240161261a565b6fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff1060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b1760008213613f1957631615e6386000526004601cfd5b7ff8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff6f8421084210842108cc6318c6db6d54be83831c1c601f161a1890811b609f90811c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506029190037d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b302017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d90565b60007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218311670de0b6b3a76400000215820261415257637c5f487d6000526004601cfd5b50670de0b6b3a7640000919091020490565b6000816000190483118202156141825763bac65e5b6000526004601cfd5b50670de0b6b3a764000091020490565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdc0d0570925a462d782136141c057919050565b680755bf798b4a1bf1e582126141de5763a37bfec96000526004601cfd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b60006143ab670de0b6b3a76400008361439286613eba565b61439c9190615a51565b6143a69190615b0d565b614192565b90505b92915050565b600080614441837e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b600160ff919091161b90920392915050565b6000808261449c576144976fffffffffffffffffffffffffffffffff86167f000000000000000000000000000000000000000000000000000000000000000461500a565b6144b7565b6144b7856fffffffffffffffffffffffffffffffff16615196565b9050600284815481106144cc576144cc615720565b906000526020600020906005020191505b60048201546fffffffffffffffffffffffffffffffff82811691161461452f57815460028054909163ffffffff1690811061451a5761451a615720565b906000526020600020906005020191506144dd565b509392505050565b600080600080600061454886614566565b935093509350935061455c8484848461496f565b9695505050505050565b600080600080600085905060006002828154811061458657614586615720565b600091825260209091206004600590920201908101549091507f00000000000000000000000000000000000000000000000000000000000000049061465d906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1611614697576040517fb34b5c2200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000815b60048301547f00000000000000000000000000000000000000000000000000000000000000049061475e906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1692508211156147d357825463ffffffff1661479d7f0000000000000000000000000000000000000000000000000000000000000004600161577e565b83036147a7578391505b600281815481106147ba576147ba615720565b906000526020600020906005020193508094505061469b565b600481810154908401546fffffffffffffffffffffffffffffffff91821691166000816fffffffffffffffffffffffffffffffff1661483c614827856fffffffffffffffffffffffffffffffff1660011c90565b6fffffffffffffffffffffffffffffffff1690565b6fffffffffffffffffffffffffffffffff16149050801561490b576000614874836fffffffffffffffffffffffffffffffff166143b4565b6fffffffffffffffffffffffffffffffff1611156148df5760006148b66148ae60016fffffffffffffffffffffffffffffffff86166158f6565b896001614453565b6003810154600490910154909c506fffffffffffffffffffffffffffffffff169a506148e59050565b6008549a505b600386015460048701549099506fffffffffffffffffffffffffffffffff169750614961565b600061492d6148ae6fffffffffffffffffffffffffffffffff8516600161591f565b6003808901546004808b015492840154930154909e506fffffffffffffffffffffffffffffffff9182169d50919b50169850505b505050505050509193509193565b60006fffffffffffffffffffffffffffffffff8416156149dc5760408051602081018790526fffffffffffffffffffffffffffffffff8087169282019290925260608101859052908316608082015260a00160405160208183030381529060405280519060200120611884565b8282604051602001614a0a9291909182526fffffffffffffffffffffffffffffffff16602082015260400190565b6040516020818303038152906040528051906020012095945050505050565b600080614ab6847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690508083036001841b600180831b0386831b17039250505092915050565b60008060008360000151600003614b1a576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020840151805160001a607f8111614b3f576000600160009450945094505050614f6e565b60b78111614c55576000614b546080836157ce565b905080876000015111614b93576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001838101517fff00000000000000000000000000000000000000000000000000000000000000169082148015614c0b57507f80000000000000000000000000000000000000000000000000000000000000007fff000000000000000000000000000000000000000000000000000000000000008216105b15614c42576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060019550935060009250614f6e915050565b60bf8111614db3576000614c6a60b7836157ce565b905080876000015111614ca9576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614d0b576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614d53576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614d5d818461577e565b895111614d96576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614da183600161577e565b9750955060009450614f6e9350505050565b60f78111614e18576000614dc860c0836157ce565b905080876000015111614e07576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600195509350849250614f6e915050565b6000614e2560f7836157ce565b905080876000015111614e64576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614ec6576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614f0e576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614f18818461577e565b895111614f51576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614f5c83600161577e565b9750955060019450614f6e9350505050565b9193909250565b60608167ffffffffffffffff811115614f9057614f90615654565b6040519080825280601f01601f191660200182016040528015614fba576020820181803683370190505b5090508115615003576000614fcf848661577e565b90506020820160005b84811015614ff0578281015182820152602001614fd8565b84811115614fff576000858301525b5050505b9392505050565b6000816150a9846fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff16116150bf5763b34b5c226000526004601cfd5b6150c883615196565b905081615167826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff16116143ae576143ab61517d83600161577e565b6fffffffffffffffffffffffffffffffff83169061523b565b6000811960018301168161522a827e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169390931c8015179392505050565b6000806152c8847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050808303600180821b0385821b179250505092915050565b60008083601f8401126152f657600080fd5b50813567ffffffffffffffff81111561530e57600080fd5b60208301915083602082850101111561532657600080fd5b9250929050565b600080600083850360a081121561534357600080fd5b608081121561535157600080fd5b50839250608084013567ffffffffffffffff81111561536f57600080fd5b61537b868287016152e4565b9497909650939450505050565b6000806040838503121561539b57600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6020810160038310615414577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b60008060006060848603121561542f57600080fd5b505081359360208301359350604090920135919050565b6000815180845260005b8181101561546c57602081850181015186830182015201615450565b8181111561547e576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006143ab6020830184615446565b6000602082840312156154d657600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff811681146154ff57600080fd5b50565b60006020828403121561551457600080fd5b8135615003816154dd565b8035801515811461552f57600080fd5b919050565b6000806000806080858703121561554a57600080fd5b8435935060208501359250604085013591506155686060860161551f565b905092959194509250565b60006020828403121561558557600080fd5b81356fffffffffffffffffffffffffffffffff8116811461500357600080fd5b600080600080600080608087890312156155be57600080fd5b863595506155ce6020880161551f565b9450604087013567ffffffffffffffff808211156155eb57600080fd5b6155f78a838b016152e4565b9096509450606089013591508082111561561057600080fd5b5061561d89828a016152e4565b979a9699509497509295939492505050565b63ffffffff841681528260208201526060604082015260006118846060830184615446565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006080828403121561569557600080fd5b6040516080810181811067ffffffffffffffff821117156156df577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b8183823760009101908152919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082198211156157915761579161574f565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036157c7576157c761574f565b5060010190565b6000828210156157e0576157e061574f565b500390565b600067ffffffffffffffff838116908316818110156158065761580661574f565b039392505050565b600067ffffffffffffffff808316818516818304811182151516156158355761583561574f565b02949350505050565b6000806040838503121561585157600080fd5b505080516020909101519092909150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000826158a0576158a0615862565b500490565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156158dd576158dd61574f565b500290565b6000826158f1576158f1615862565b500690565b60006fffffffffffffffffffffffffffffffff838116908316818110156158065761580661574f565b60006fffffffffffffffffffffffffffffffff80831681851680830382111561594a5761594a61574f565b01949350505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b6060815260006159b0606083018789615953565b82810360208401526159c3818688615953565b9150508260408301529695505050505050565b6000602082840312156159e857600080fd5b5051919050565b600060ff821660ff841680821015615a0957615a0961574f565b90039392505050565b600060ff831680615a2557615a25615862565b8060ff84160691505092915050565b600060208284031215615a4657600080fd5b8151615003816154dd565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615615a9257615a9261574f565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615615acd57615acd61574f565b60008712925087820587128484161615615ae957615ae961574f565b87850587128184161615615aff57615aff61574f565b505050929093029392505050565b600082615b1c57615b1c615862565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615615b7057615b7061574f565b50059056fea164736f6c634300080f000a"; bytes internal constant acc28Code = - hex"6080604052600436106103085760003560e01c806370872aa51161019a578063c6f0308c116100e1578063ec5e63081161008a578063fa24f74311610064578063fa24f74314610b94578063fa315aa914610bb8578063fe2bbeb214610beb57600080fd5b8063ec5e630814610b11578063eff0f59214610b44578063f8f43ff614610b7457600080fd5b8063d6ae3cd5116100bb578063d6ae3cd514610a8b578063d8cc1a3c14610abe578063dabd396d14610ade57600080fd5b8063c6f0308c146109b3578063cf09e0d014610a3d578063d5d44d8014610a5e57600080fd5b8063a445ece611610143578063bcef3b551161011d578063bcef3b5514610933578063bd8da95614610973578063c395e1ca1461099357600080fd5b8063a445ece6146107f3578063a8e4fb90146108bf578063bbdc02db146108f257600080fd5b80638980e0cc116101745780638980e0cc1461076b5780638b85902b146107805780638d450a95146107c057600080fd5b806370872aa51461073b5780637b0f0adc146107505780638129fc1c1461076357600080fd5b80633fc8cef31161025e5780635c0cba33116102075780636361506d116101e15780636361506d146106b55780636b6716c0146106f55780636f0344091461072857600080fd5b80635c0cba331461064d578063609d33341461068057806360e274641461069557600080fd5b806354fd4d501161023857806354fd4d50146105a757806357da950e146105fd5780635a5fa2d91461062d57600080fd5b80633fc8cef31461052e578063472777c614610561578063534db0e21461057457600080fd5b80632810e1d6116102c057806337b1b2291161029a57806337b1b2291461047b5780633a768463146104bb5780633e3ac912146104ee57600080fd5b80632810e1d6146103f45780632ad69aeb1461040957806330dbe5701461042957600080fd5b806319effeb4116102f157806319effeb41461034f578063200d2ed21461039a57806325fc2ace146103d557600080fd5b8063019351301461030d57806303c2924d1461032f575b600080fd5b34801561031957600080fd5b5061032d6103283660046155a8565b610c1b565b005b34801561033b57600080fd5b5061032d61034a366004615603565b610f3c565b34801561035b57600080fd5b5060005461037c9068010000000000000000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020015b60405180910390f35b3480156103a657600080fd5b506000546103c890700100000000000000000000000000000000900460ff1681565b6040516103919190615654565b3480156103e157600080fd5b506008545b604051908152602001610391565b34801561040057600080fd5b506103c86115e2565b34801561041557600080fd5b506103e6610424366004615603565b611887565b34801561043557600080fd5b506001546104569073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610391565b34801561048757600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90033560601c610456565b3480156104c757600080fd5b507f0000000000000000000000001c0e3b8e58dd91536caf37a6009536255a7816a6610456565b3480156104fa57600080fd5b5060005461051e907201000000000000000000000000000000000000900460ff1681565b6040519015158152602001610391565b34801561053a57600080fd5b507f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6610456565b61032d61056f366004615695565b6118bd565b34801561058057600080fd5b507f0000000000000000000000006925b8704ff96dee942623d6fb5e946ef5884b63610456565b3480156105b357600080fd5b506105f06040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b604051610391919061572c565b34801561060957600080fd5b50600854600954610618919082565b60408051928352602083019190915201610391565b34801561063957600080fd5b506103e661064836600461573f565b6118cf565b34801561065957600080fd5b507f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e4610456565b34801561068c57600080fd5b506105f0611909565b3480156106a157600080fd5b5061032d6106b036600461577d565b611917565b3480156106c157600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003603401356103e6565b34801561070157600080fd5b507f000000000000000000000000000000000000000000000000000000000000000061037c565b61032d6107363660046157af565b611abe565b34801561074757600080fd5b506009546103e6565b61032d61075e366004615695565b611b7f565b61032d611b8c565b34801561077757600080fd5b506002546103e6565b34801561078c57600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401356103e6565b3480156107cc57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103e6565b3480156107ff57600080fd5b5061086b61080e36600461573f565b6007602052600090815260409020805460019091015460ff821691610100810463ffffffff1691650100000000009091046fffffffffffffffffffffffffffffffff169073ffffffffffffffffffffffffffffffffffffffff1684565b60408051941515855263ffffffff90931660208501526fffffffffffffffffffffffffffffffff9091169183019190915273ffffffffffffffffffffffffffffffffffffffff166060820152608001610391565b3480156108cb57600080fd5b507f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8610456565b3480156108fe57600080fd5b5060405163ffffffff7f0000000000000000000000000000000000000000000000000000000000000001168152602001610391565b34801561093f57600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003601401356103e6565b34801561097f57600080fd5b5061037c61098e36600461573f565b611c05565b34801561099f57600080fd5b506103e66109ae3660046157ee565b611de4565b3480156109bf57600080fd5b506109d36109ce36600461573f565b611fc7565b6040805163ffffffff909816885273ffffffffffffffffffffffffffffffffffffffff968716602089015295909416948601949094526fffffffffffffffffffffffffffffffff9182166060860152608085015291821660a08401521660c082015260e001610391565b348015610a4957600080fd5b5060005461037c9067ffffffffffffffff1681565b348015610a6a57600080fd5b506103e6610a7936600461577d565b60036020526000908152604090205481565b348015610a9757600080fd5b507f00000000000000000000000000000000000000000000000000000000000003856103e6565b348015610aca57600080fd5b5061032d610ad9366004615820565b61205e565b348015610aea57600080fd5b507f00000000000000000000000000000000000000000000000000000000000004b061037c565b348015610b1d57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000046103e6565b348015610b5057600080fd5b5061051e610b5f36600461573f565b60046020526000908152604090205460ff1681565b348015610b8057600080fd5b5061032d610b8f366004615695565b612123565b348015610ba057600080fd5b50610ba9612575565b604051610391939291906158aa565b348015610bc457600080fd5b507f00000000000000000000000000000000000000000000000000000000000000086103e6565b348015610bf757600080fd5b5061051e610c0636600461573f565b60066020526000908152604090205460ff1681565b60008054700100000000000000000000000000000000900460ff166002811115610c4757610c47615625565b14610c7e576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff1615610cd1576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610d08367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013590565b90565b610d1f610d1a368690038601866158fe565b6125d5565b14610d56576040517f9cc00b5b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82606001358282604051610d6b92919061598b565b604051809103902014610daa576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610df3610dee84848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061263192505050565b61269e565b90506000610e1a82600881518110610e0d57610e0d61599b565b6020026020010151612854565b9050602081511115610e58576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602081810151825190910360031b1c367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401358103610ecd576040517fb8ed883000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050600180547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790555050600080547fffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffff1672010000000000000000000000000000000000001790555050565b60008054700100000000000000000000000000000000900460ff166002811115610f6857610f68615625565b14610f9f576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028381548110610fb457610fb461599b565b906000526020600020906005020190506000610fcf84611c05565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b081169082161015611038576040517ff2440b5300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008481526006602052604090205460ff1615611081576040517ff1a9458100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600084815260056020526040902080548015801561109e57508515155b15611139578354640100000000900473ffffffffffffffffffffffffffffffffffffffff16600081156110d157816110ed565b600186015473ffffffffffffffffffffffffffffffffffffffff165b90506110f98187612908565b50505060009485525050600660205250506040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000868152600760209081526040918290208251608081018452815460ff81161515808352610100820463ffffffff16948301949094526501000000000090046fffffffffffffffffffffffffffffffff16938101939093526001015473ffffffffffffffffffffffffffffffffffffffff1660608301526111dc576fffffffffffffffffffffffffffffffff60408201526001815260008690036111dc578195505b600086826020015163ffffffff166111f491906159f9565b905060008382116112055781611207565b835b602084015190915063ffffffff165b818110156113535760008682815481106112325761123261599b565b6000918252602080832090910154808352600690915260409091205490915060ff1661128a576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006002828154811061129f5761129f61599b565b600091825260209091206005909102018054909150640100000000900473ffffffffffffffffffffffffffffffffffffffff161580156112fc5750600481015460408701516fffffffffffffffffffffffffffffffff9182169116115b1561133e57600181015473ffffffffffffffffffffffffffffffffffffffff16606087015260048101546fffffffffffffffffffffffffffffffff1660408701525b5050808061134b90615a11565b915050611216565b5063ffffffff818116602085810191825260008c81526007909152604090819020865181549351928801517fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000009094169015157fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ff161761010092909416918202939093177fffffffffffffffffffffff00000000000000000000000000000000ffffffffff16650100000000006fffffffffffffffffffffffffffffffff909316929092029190911782556060850151600190920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909316929092179091558490036115d757606083015160008a815260066020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055891580156114d357506000547201000000000000000000000000000000000000900460ff165b156115485760015473ffffffffffffffffffffffffffffffffffffffff166114fb818a612908565b885473ffffffffffffffffffffffffffffffffffffffff909116640100000000027fffffffffffffffff0000000000000000000000000000000000000000ffffffff9091161788556115d5565b61158f73ffffffffffffffffffffffffffffffffffffffff82161561156d5781611589565b600189015473ffffffffffffffffffffffffffffffffffffffff165b89612908565b87547fffffffffffffffff0000000000000000000000000000000000000000ffffffff1664010000000073ffffffffffffffffffffffffffffffffffffffff8316021788555b505b505050505050505050565b600080600054700100000000000000000000000000000000900460ff16600281111561161057611610615625565b14611647576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805260066020527f54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f85460ff166116ab576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff1660026000815481106116d7576116d761599b565b6000918252602090912060059091020154640100000000900473ffffffffffffffffffffffffffffffffffffffff1614611712576001611715565b60025b6000805467ffffffffffffffff421668010000000000000000027fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff82168117835592935083927fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffff000000000000000000ffffffffffffffff909116177001000000000000000000000000000000008360028111156117c6576117c6615625565b0217905560028111156117db576117db615625565b6040517f5e186f09b9c93491f14e277eea7faa5de6a2d4bda75a79af7a3684fbfb42da6090600090a27f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e473ffffffffffffffffffffffffffffffffffffffff1663838c2d1e6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561186c57600080fd5b505af1158015611880573d6000803e3d6000fd5b5050505090565b600560205281600052604060002081815481106118a357600080fd5b90600052602060002001600091509150505481565b905090565b6118ca8383836001611abe565b505050565b6000818152600760209081526040808320600590925282208054825461190090610100900463ffffffff1682615a49565b95945050505050565b60606118b860546020612a09565b73ffffffffffffffffffffffffffffffffffffffff811660009081526003602052604081208054908290559081900361197c576040517f17bfe5f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390527f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6169063f3fef3a390604401600060405180830381600087803b158015611a0c57600080fd5b505af1158015611a20573d6000803e3d6000fd5b5050505060008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114611a7e576040519150601f19603f3d011682016040523d82523d6000602084013e611a83565b606091505b50509050806118ca576040517f83e6cc6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8161480611b3757503373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006925b8704ff96dee942623d6fb5e946ef5884b6316145b611b6d576040517fd386ef3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611b7984848484612a5b565b50505050565b6118ca8383836000611abe565b3273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c81614611bfb576040517fd386ef3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c036133fc565b565b600080600054700100000000000000000000000000000000900460ff166002811115611c3357611c33615625565b14611c6a576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028381548110611c7f57611c7f61599b565b600091825260208220600590910201805490925063ffffffff90811614611cee57815460028054909163ffffffff16908110611cbd57611cbd61599b565b906000526020600020906005020160040160109054906101000a90046fffffffffffffffffffffffffffffffff1690505b6004820154600090611d2690700100000000000000000000000000000000900467ffffffffffffffff165b67ffffffffffffffff1690565b611d3a9067ffffffffffffffff1642615a49565b611d59611d19846fffffffffffffffffffffffffffffffff1660401c90565b67ffffffffffffffff16611d6d91906159f9565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b01667ffffffffffffffff168167ffffffffffffffff1611611dba5780611900565b7f00000000000000000000000000000000000000000000000000000000000004b095945050505050565b600080611e83836fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690507f0000000000000000000000000000000000000000000000000000000000000008811115611ee2576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b642e90edd00062061a806311e1a3006000611efd8383615a8f565b9050670de0b6b3a76400006000611f34827f0000000000000000000000000000000000000000000000000000000000000008615aa3565b90506000611f52611f4d670de0b6b3a764000086615aa3565b613955565b90506000611f608484613bb0565b90506000611f6e8383613bff565b90506000611f7b82613c2d565b90506000611f9a82611f95670de0b6b3a76400008f615aa3565b613e15565b90506000611fa88b83613bff565b9050611fb4818d615aa3565b9f9e505050505050505050505050505050565b60028181548110611fd757600080fd5b60009182526020909120600590910201805460018201546002830154600384015460049094015463ffffffff8416955064010000000090930473ffffffffffffffffffffffffffffffffffffffff908116949216926fffffffffffffffffffffffffffffffff91821692918082169170010000000000000000000000000000000090041687565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c81614806120d757503373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006925b8704ff96dee942623d6fb5e946ef5884b6316145b61210d576040517fd386ef3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61211b868686868686613e4f565b505050505050565b60008054700100000000000000000000000000000000900460ff16600281111561214f5761214f615625565b14612186576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806000806121958661447e565b935093509350935060006121ab85858585614887565b905060007f0000000000000000000000001c0e3b8e58dd91536caf37a6009536255a7816a673ffffffffffffffffffffffffffffffffffffffff16637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561221a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061223e9190615ae0565b9050600189036123365773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a8461229a367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036034013590565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b16815260048101939093526024830191909152604482015260206064820152608481018a905260a4015b6020604051808303816000875af115801561230c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123309190615afd565b506115d7565b600289036123625773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a848961229a565b6003890361238e5773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a848761229a565b600489036124aa5760006123d46fffffffffffffffffffffffffffffffff85167f0000000000000000000000000000000000000000000000000000000000000004614941565b6009546123e191906159f9565b6123ec9060016159f9565b905073ffffffffffffffffffffffffffffffffffffffff82166352f0f3ad8b8560405160e084901b7fffffffff000000000000000000000000000000000000000000000000000000001681526004810192909252602482015260c084901b604482015260086064820152608481018b905260a4016020604051808303816000875af115801561247f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124a39190615afd565b50506115d7565b60058903612543576040517f52f0f3ad000000000000000000000000000000000000000000000000000000008152600481018a9052602481018390527f000000000000000000000000000000000000000000000000000000000000038560c01b6044820152600860648201526084810188905273ffffffffffffffffffffffffffffffffffffffff8216906352f0f3ad9060a4016122ed565b6040517fff137e6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000001367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013560606125ce611909565b9050909192565b60008160000151826020015183604001518460600151604051602001612614949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b60408051808201909152600080825260208201528151600003612680576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b606060008060006126ae856149ef565b9194509250905060018160018111156126c9576126c9615625565b14612700576040517f4b9c6abe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b845161270c83856159f9565b14612743576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516020808252610420820190925290816020015b604080518082019091526000808252602082015281526020019060019003908161275a5790505093506000835b8651811015612848576000806127cd6040518060400160405280858c600001516127b19190615a49565b8152602001858c602001516127c691906159f9565b90526149ef565b5091509150604051806040016040528083836127e991906159f9565b8152602001848b602001516127fe91906159f9565b8152508885815181106128135761281361599b565b60209081029190910101526128296001856159f9565b935061283581836159f9565b61283f90846159f9565b92505050612787565b50845250919392505050565b60606000806000612864856149ef565b91945092509050600081600181111561287f5761287f615625565b146128b6576040517f1ff9b2e400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6128c082846159f9565b8551146128f9576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61190085602001518484614e8d565b600281015473ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080546fffffffffffffffffffffffffffffffff909316928392906129579084906159f9565b90915550506040517f7eee288d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152602482018390527f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa61690637eee288d90604401600060405180830381600087803b1580156129ec57600080fd5b505af1158015612a00573d6000803e3d6000fd5b50505050505050565b604051818152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90038284820160208401378260208301016000815260208101604052505092915050565b60008054700100000000000000000000000000000000900460ff166002811115612a8757612a87615625565b14612abe576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028481548110612ad357612ad361599b565b60009182526020918290206040805160e0810182526005909302909101805463ffffffff8116845273ffffffffffffffffffffffffffffffffffffffff64010000000090910481169484019490945260018101549093169082015260028201546fffffffffffffffffffffffffffffffff908116606083015260038301546080830181905260049093015480821660a084015270010000000000000000000000000000000090041660c082015291508514612bba576040517f3014033200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60a0810151600083156fffffffffffffffffffffffffffffffff83161760011b90506000612c7a826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050861580612cb55750612cb27f000000000000000000000000000000000000000000000000000000000000000460026159f9565b81145b8015612cbf575084155b15612cf6576040517fa42637bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff168015612d1c575086155b15612d53576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000008811115612dad576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612dd87f000000000000000000000000000000000000000000000000000000000000000460016159f9565b8103612dea57612dea86888588614f22565b34612df483611de4565b14612e2b576040517f8620aa1900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612e3688611c05565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b0811690821603612e9e576040517f3381d11400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001667ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b016612efe9190615b16565b67ffffffffffffffff16612f198267ffffffffffffffff1690565b67ffffffffffffffff161115612ffb576000612f5660017f0000000000000000000000000000000000000000000000000000000000000004615a49565b8314612f8c5767ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016612fc1565b612fc17f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff166002615b3f565b9050612ff7817f00000000000000000000000000000000000000000000000000000000000004b067ffffffffffffffff16615b16565b9150505b6000604082901b42176000898152608086901b6fffffffffffffffffffffffffffffffff8c1617602052604081209192509060008181526004602052604090205490915060ff1615613079576040517f80497e3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60016004600083815260200190815260200160002060006101000a81548160ff02191690831515021790555060026040518060e001604052808c63ffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020013373ffffffffffffffffffffffffffffffffffffffff168152602001346fffffffffffffffffffffffffffffffff1681526020018b8152602001876fffffffffffffffffffffffffffffffff168152602001846fffffffffffffffffffffffffffffffff16815250908060018154018082558091505060019003906000526020600020906005020160009091909190915060008201518160000160006101000a81548163ffffffff021916908363ffffffff16021790555060208201518160000160046101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060608201518160020160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506080820151816003015560a08201518160040160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060c08201518160040160106101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505050600560008b8152602001908152602001600020600160028054905061330f9190615a49565b81546001810183556000928352602083200155604080517fd0e30db0000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6169263d0e30db09234926004808301939282900301818588803b1580156133a757600080fd5b505af11580156133bb573d6000803e3d6000fd5b50506040513393508c92508d91507f9b3245740ec3b155098a55be84957a4da13eaf7f14a8bc6f53126c0b9350f2be90600090a45050505050505050505050565b60005471010000000000000000000000000000000000900460ff161561344e576040517f0dc149f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f7258a80700000000000000000000000000000000000000000000000000000000815263ffffffff7f0000000000000000000000000000000000000000000000000000000000000001166004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e41690637258a807906024016040805180830381865afa158015613502573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135269190615b6f565b909250905081613562576040517f6a6bc3b200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080518082019091528281526020018190526008829055600981905536607a1461359557639824bdab6000526004601cfd5b80367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401351161362f576040517ff40239db000000000000000000000000000000000000000000000000000000008152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013560048201526024015b60405180910390fd5b6040805160e08101825263ffffffff8082526000602083018181527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe369081013560f01c90038035606090811c868801908152346fffffffffffffffffffffffffffffffff81811693890193845260149094013560808901908152600160a08a0181815242871660c08c019081526002805493840181558a529a5160059092027f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace81018054995173ffffffffffffffffffffffffffffffffffffffff908116640100000000027fffffffffffffffff000000000000000000000000000000000000000000000000909b1694909c16939093179890981790915592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf87018054918a167fffffffffffffffffffffffff000000000000000000000000000000000000000090921691909117905592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad0860180549186167fffffffffffffffffffffffffffffffff0000000000000000000000000000000090921691909117905591517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad185015551955182167001000000000000000000000000000000000295909116949094177f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad29091015580547fffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffff167101000000000000000000000000000000000017815583517fd0e30db000000000000000000000000000000000000000000000000000000000815293517f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa69092169363d0e30db093926004828101939282900301818588803b15801561390457600080fd5b505af1158015613918573d6000803e3d6000fd5b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000164267ffffffffffffffff161790555050505050565b6fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff1060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b17600082136139b457631615e6386000526004601cfd5b7ff8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff6f8421084210842108cc6318c6db6d54be83831c1c601f161a1890811b609f90811c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506029190037d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b302017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d90565b60007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218311670de0b6b3a764000002158202613bed57637c5f487d6000526004601cfd5b50670de0b6b3a7640000919091020490565b600081600019048311820215613c1d5763bac65e5b6000526004601cfd5b50670de0b6b3a764000091020490565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdc0d0570925a462d78213613c5b57919050565b680755bf798b4a1bf1e58212613c795763a37bfec96000526004601cfd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b6000613e46670de0b6b3a764000083613e2d86613955565b613e379190615b93565b613e419190615c4f565b613c2d565b90505b92915050565b60008054700100000000000000000000000000000000900460ff166002811115613e7b57613e7b615625565b14613eb2576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028781548110613ec757613ec761599b565b6000918252602082206005919091020160048101549092506fffffffffffffffffffffffffffffffff16908715821760011b9050613f267f000000000000000000000000000000000000000000000000000000000000000860016159f9565b613fc2826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1614613ffc576040517f5f53dd9800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008089156140f35761404f7f00000000000000000000000000000000000000000000000000000000000000047f0000000000000000000000000000000000000000000000000000000000000008615a49565b6001901b61406e846fffffffffffffffffffffffffffffffff166150d3565b6fffffffffffffffffffffffffffffffff1661408a9190615cb7565b156140c7576140be6140af60016fffffffffffffffffffffffffffffffff8716615ccb565b865463ffffffff166000615172565b600301546140e9565b7f00000000000000000000000000000000000000000000000000000000000000005b915084905061411d565b6003850154915061411a6140af6fffffffffffffffffffffffffffffffff86166001615cf4565b90505b600882901b60088a8a60405161413492919061598b565b6040518091039020901b14614175576040517f696550ff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006141808c615256565b9050600061418f836003015490565b6040517fe14ced320000000000000000000000000000000000000000000000000000000081527f0000000000000000000000001c0e3b8e58dd91536caf37a6009536255a7816a673ffffffffffffffffffffffffffffffffffffffff169063e14ced3290614209908f908f908f908f908a90600401615d71565b6020604051808303816000875af1158015614228573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061424c9190615afd565b6004850154911491506000906002906142f7906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b614393896fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b61439d9190615dab565b6143a79190615dce565b60ff1615905081151581036143e8576040517ffb4e40dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8754640100000000900473ffffffffffffffffffffffffffffffffffffffff161561443f576040517f9071e6af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505085547fffffffffffffffff0000000000000000000000000000000000000000ffffffff163364010000000002179095555050505050505050505050565b600080600080600085905060006002828154811061449e5761449e61599b565b600091825260209091206004600590920201908101549091507f000000000000000000000000000000000000000000000000000000000000000490614575906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff16116145af576040517fb34b5c2200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000815b60048301547f000000000000000000000000000000000000000000000000000000000000000490614676906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1692508211156146eb57825463ffffffff166146b57f000000000000000000000000000000000000000000000000000000000000000460016159f9565b83036146bf578391505b600281815481106146d2576146d261599b565b90600052602060002090600502019350809450506145b3565b600481810154908401546fffffffffffffffffffffffffffffffff91821691166000816fffffffffffffffffffffffffffffffff1661475461473f856fffffffffffffffffffffffffffffffff1660011c90565b6fffffffffffffffffffffffffffffffff1690565b6fffffffffffffffffffffffffffffffff16149050801561482357600061478c836fffffffffffffffffffffffffffffffff166150d3565b6fffffffffffffffffffffffffffffffff1611156147f75760006147ce6147c660016fffffffffffffffffffffffffffffffff8616615ccb565b896001615172565b6003810154600490910154909c506fffffffffffffffffffffffffffffffff169a506147fd9050565b6008549a505b600386015460048701549099506fffffffffffffffffffffffffffffffff169750614879565b60006148456147c66fffffffffffffffffffffffffffffffff85166001615cf4565b6003808901546004808b015492840154930154909e506fffffffffffffffffffffffffffffffff9182169d50919b50169850505b505050505050509193509193565b60006fffffffffffffffffffffffffffffffff8416156148f45760408051602081018790526fffffffffffffffffffffffffffffffff8087169282019290925260608101859052908316608082015260a00160405160208183030381529060405280519060200120611900565b82826040516020016149229291909182526fffffffffffffffffffffffffffffffff16602082015260400190565b6040516020818303038152906040528051906020012095945050505050565b6000806149ce847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690508083036001841b600180831b0386831b17039250505092915050565b60008060008360000151600003614a32576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020840151805160001a607f8111614a57576000600160009450945094505050614e86565b60b78111614b6d576000614a6c608083615a49565b905080876000015111614aab576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001838101517fff00000000000000000000000000000000000000000000000000000000000000169082148015614b2357507f80000000000000000000000000000000000000000000000000000000000000007fff000000000000000000000000000000000000000000000000000000000000008216105b15614b5a576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060019550935060009250614e86915050565b60bf8111614ccb576000614b8260b783615a49565b905080876000015111614bc1576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614c23576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614c6b576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614c7581846159f9565b895111614cae576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614cb98360016159f9565b9750955060009450614e869350505050565b60f78111614d30576000614ce060c083615a49565b905080876000015111614d1f576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600195509350849250614e86915050565b6000614d3d60f783615a49565b905080876000015111614d7c576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614dde576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614e26576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614e3081846159f9565b895111614e69576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614e748360016159f9565b9750955060019450614e869350505050565b9193909250565b60608167ffffffffffffffff811115614ea857614ea86158cf565b6040519080825280601f01601f191660200182016040528015614ed2576020820181803683370190505b5090508115614f1b576000614ee784866159f9565b90506020820160005b84811015614f08578281015182820152602001614ef0565b84811115614f17576000858301525b5050505b9392505050565b6000614f416fffffffffffffffffffffffffffffffff84166001615cf4565b90506000614f5182866001615172565b9050600086901a838061503d5750614f8a60027f0000000000000000000000000000000000000000000000000000000000000004615cb7565b600483015460029061502e906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b6150389190615dce565b60ff16145b156150955760ff811660011480615057575060ff81166002145b615090576040517ff40239db00000000000000000000000000000000000000000000000000000000815260048101889052602401613626565b612a00565b60ff811615612a00576040517ff40239db00000000000000000000000000000000000000000000000000000000815260048101889052602401613626565b600080615160837e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b600160ff919091161b90920392915050565b600080826151bb576151b66fffffffffffffffffffffffffffffffff86167f0000000000000000000000000000000000000000000000000000000000000004615285565b6151d6565b6151d6856fffffffffffffffffffffffffffffffff16615411565b9050600284815481106151eb576151eb61599b565b906000526020600020906005020191505b60048201546fffffffffffffffffffffffffffffffff82811691161461524e57815460028054909163ffffffff169081106152395761523961599b565b906000526020600020906005020191506151fc565b509392505050565b60008060008060006152678661447e565b935093509350935061527b84848484614887565b9695505050505050565b600081615324846fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff161161533a5763b34b5c226000526004601cfd5b61534383615411565b9050816153e2826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1611613e4957613e466153f88360016159f9565b6fffffffffffffffffffffffffffffffff8316906154b6565b600081196001830116816154a5827e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169390931c8015179392505050565b600080615543847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050808303600180821b0385821b179250505092915050565b60008083601f84011261557157600080fd5b50813567ffffffffffffffff81111561558957600080fd5b6020830191508360208285010111156155a157600080fd5b9250929050565b600080600083850360a08112156155be57600080fd5b60808112156155cc57600080fd5b50839250608084013567ffffffffffffffff8111156155ea57600080fd5b6155f68682870161555f565b9497909650939450505050565b6000806040838503121561561657600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b602081016003831061568f577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b6000806000606084860312156156aa57600080fd5b505081359360208301359350604090920135919050565b6000815180845260005b818110156156e7576020818501810151868301820152016156cb565b818111156156f9576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000613e4660208301846156c1565b60006020828403121561575157600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff8116811461577a57600080fd5b50565b60006020828403121561578f57600080fd5b8135614f1b81615758565b803580151581146157aa57600080fd5b919050565b600080600080608085870312156157c557600080fd5b8435935060208501359250604085013591506157e36060860161579a565b905092959194509250565b60006020828403121561580057600080fd5b81356fffffffffffffffffffffffffffffffff81168114614f1b57600080fd5b6000806000806000806080878903121561583957600080fd5b863595506158496020880161579a565b9450604087013567ffffffffffffffff8082111561586657600080fd5b6158728a838b0161555f565b9096509450606089013591508082111561588b57600080fd5b5061589889828a0161555f565b979a9699509497509295939492505050565b63ffffffff8416815282602082015260606040820152600061190060608301846156c1565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006080828403121561591057600080fd5b6040516080810181811067ffffffffffffffff8211171561595a577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b8183823760009101908152919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115615a0c57615a0c6159ca565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203615a4257615a426159ca565b5060010190565b600082821015615a5b57615a5b6159ca565b500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082615a9e57615a9e615a60565b500490565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615615adb57615adb6159ca565b500290565b600060208284031215615af257600080fd5b8151614f1b81615758565b600060208284031215615b0f57600080fd5b5051919050565b600067ffffffffffffffff83811690831681811015615b3757615b376159ca565b039392505050565b600067ffffffffffffffff80831681851681830481118215151615615b6657615b666159ca565b02949350505050565b60008060408385031215615b8257600080fd5b505080516020909101519092909150565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615615bd457615bd46159ca565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615615c0f57615c0f6159ca565b60008712925087820587128484161615615c2b57615c2b6159ca565b87850587128184161615615c4157615c416159ca565b505050929093029392505050565b600082615c5e57615c5e615a60565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615615cb257615cb26159ca565b500590565b600082615cc657615cc6615a60565b500690565b60006fffffffffffffffffffffffffffffffff83811690831681811015615b3757615b376159ca565b60006fffffffffffffffffffffffffffffffff808316818516808303821115615d1f57615d1f6159ca565b01949350505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b606081526000615d85606083018789615d28565b8281036020840152615d98818688615d28565b9150508260408301529695505050505050565b600060ff821660ff841680821015615dc557615dc56159ca565b90039392505050565b600060ff831680615de157615de1615a60565b8060ff8416069150509291505056fea164736f6c634300080f000a"; + hex"6080604052600436106103085760003560e01c806370872aa51161019a578063c6f0308c116100e1578063ec5e63081161008a578063fa24f74311610064578063fa24f74314610b94578063fa315aa914610bb8578063fe2bbeb214610beb57600080fd5b8063ec5e630814610b11578063eff0f59214610b44578063f8f43ff614610b7457600080fd5b8063d6ae3cd5116100bb578063d6ae3cd514610a8b578063d8cc1a3c14610abe578063dabd396d14610ade57600080fd5b8063c6f0308c146109b3578063cf09e0d014610a3d578063d5d44d8014610a5e57600080fd5b8063a445ece611610143578063bcef3b551161011d578063bcef3b5514610933578063bd8da95614610973578063c395e1ca1461099357600080fd5b8063a445ece6146107f3578063a8e4fb90146108bf578063bbdc02db146108f257600080fd5b80638980e0cc116101745780638980e0cc1461076b5780638b85902b146107805780638d450a95146107c057600080fd5b806370872aa51461073b5780637b0f0adc146107505780638129fc1c1461076357600080fd5b80633fc8cef31161025e5780635c0cba33116102075780636361506d116101e15780636361506d146106b55780636b6716c0146106f55780636f0344091461072857600080fd5b80635c0cba331461064d578063609d33341461068057806360e274641461069557600080fd5b806354fd4d501161023857806354fd4d50146105a757806357da950e146105fd5780635a5fa2d91461062d57600080fd5b80633fc8cef31461052e578063472777c614610561578063534db0e21461057457600080fd5b80632810e1d6116102c057806337b1b2291161029a57806337b1b2291461047b5780633a768463146104bb5780633e3ac912146104ee57600080fd5b80632810e1d6146103f45780632ad69aeb1461040957806330dbe5701461042957600080fd5b806319effeb4116102f157806319effeb41461034f578063200d2ed21461039a57806325fc2ace146103d557600080fd5b8063019351301461030d57806303c2924d1461032f575b600080fd5b34801561031957600080fd5b5061032d6103283660046155a8565b610c1b565b005b34801561033b57600080fd5b5061032d61034a366004615603565b610f3c565b34801561035b57600080fd5b5060005461037c9068010000000000000000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020015b60405180910390f35b3480156103a657600080fd5b506000546103c890700100000000000000000000000000000000900460ff1681565b6040516103919190615654565b3480156103e157600080fd5b506008545b604051908152602001610391565b34801561040057600080fd5b506103c86115e2565b34801561041557600080fd5b506103e6610424366004615603565b611887565b34801561043557600080fd5b506001546104569073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610391565b34801561048757600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90033560601c610456565b3480156104c757600080fd5b507f00000000000000000000000028bf1582225713139c0e898326db808b6484cfd4610456565b3480156104fa57600080fd5b5060005461051e907201000000000000000000000000000000000000900460ff1681565b6040519015158152602001610391565b34801561053a57600080fd5b507f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6610456565b61032d61056f366004615695565b6118bd565b34801561058057600080fd5b507f0000000000000000000000006925b8704ff96dee942623d6fb5e946ef5884b63610456565b3480156105b357600080fd5b506105f06040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b604051610391919061572c565b34801561060957600080fd5b50600854600954610618919082565b60408051928352602083019190915201610391565b34801561063957600080fd5b506103e661064836600461573f565b6118cf565b34801561065957600080fd5b507f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e4610456565b34801561068c57600080fd5b506105f0611909565b3480156106a157600080fd5b5061032d6106b036600461577d565b611917565b3480156106c157600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003603401356103e6565b34801561070157600080fd5b507f000000000000000000000000000000000000000000000000000000000000000061037c565b61032d6107363660046157af565b611abe565b34801561074757600080fd5b506009546103e6565b61032d61075e366004615695565b611b7f565b61032d611b8c565b34801561077757600080fd5b506002546103e6565b34801561078c57600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401356103e6565b3480156107cc57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103e6565b3480156107ff57600080fd5b5061086b61080e36600461573f565b6007602052600090815260409020805460019091015460ff821691610100810463ffffffff1691650100000000009091046fffffffffffffffffffffffffffffffff169073ffffffffffffffffffffffffffffffffffffffff1684565b60408051941515855263ffffffff90931660208501526fffffffffffffffffffffffffffffffff9091169183019190915273ffffffffffffffffffffffffffffffffffffffff166060820152608001610391565b3480156108cb57600080fd5b507f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8610456565b3480156108fe57600080fd5b5060405163ffffffff7f0000000000000000000000000000000000000000000000000000000000000001168152602001610391565b34801561093f57600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003601401356103e6565b34801561097f57600080fd5b5061037c61098e36600461573f565b611c05565b34801561099f57600080fd5b506103e66109ae3660046157ee565b611de4565b3480156109bf57600080fd5b506109d36109ce36600461573f565b611fc7565b6040805163ffffffff909816885273ffffffffffffffffffffffffffffffffffffffff968716602089015295909416948601949094526fffffffffffffffffffffffffffffffff9182166060860152608085015291821660a08401521660c082015260e001610391565b348015610a4957600080fd5b5060005461037c9067ffffffffffffffff1681565b348015610a6a57600080fd5b506103e6610a7936600461577d565b60036020526000908152604090205481565b348015610a9757600080fd5b507f00000000000000000000000000000000000000000000000000000000000003856103e6565b348015610aca57600080fd5b5061032d610ad9366004615820565b61205e565b348015610aea57600080fd5b507f00000000000000000000000000000000000000000000000000000000000004b061037c565b348015610b1d57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000046103e6565b348015610b5057600080fd5b5061051e610b5f36600461573f565b60046020526000908152604090205460ff1681565b348015610b8057600080fd5b5061032d610b8f366004615695565b612123565b348015610ba057600080fd5b50610ba9612575565b604051610391939291906158aa565b348015610bc457600080fd5b507f00000000000000000000000000000000000000000000000000000000000000086103e6565b348015610bf757600080fd5b5061051e610c0636600461573f565b60066020526000908152604090205460ff1681565b60008054700100000000000000000000000000000000900460ff166002811115610c4757610c47615625565b14610c7e576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff1615610cd1576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610d08367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013590565b90565b610d1f610d1a368690038601866158fe565b6125d5565b14610d56576040517f9cc00b5b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82606001358282604051610d6b92919061598b565b604051809103902014610daa576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610df3610dee84848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061263192505050565b61269e565b90506000610e1a82600881518110610e0d57610e0d61599b565b6020026020010151612854565b9050602081511115610e58576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602081810151825190910360031b1c367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401358103610ecd576040517fb8ed883000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050600180547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790555050600080547fffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffff1672010000000000000000000000000000000000001790555050565b60008054700100000000000000000000000000000000900460ff166002811115610f6857610f68615625565b14610f9f576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028381548110610fb457610fb461599b565b906000526020600020906005020190506000610fcf84611c05565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b081169082161015611038576040517ff2440b5300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008481526006602052604090205460ff1615611081576040517ff1a9458100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600084815260056020526040902080548015801561109e57508515155b15611139578354640100000000900473ffffffffffffffffffffffffffffffffffffffff16600081156110d157816110ed565b600186015473ffffffffffffffffffffffffffffffffffffffff165b90506110f98187612908565b50505060009485525050600660205250506040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000868152600760209081526040918290208251608081018452815460ff81161515808352610100820463ffffffff16948301949094526501000000000090046fffffffffffffffffffffffffffffffff16938101939093526001015473ffffffffffffffffffffffffffffffffffffffff1660608301526111dc576fffffffffffffffffffffffffffffffff60408201526001815260008690036111dc578195505b600086826020015163ffffffff166111f491906159f9565b905060008382116112055781611207565b835b602084015190915063ffffffff165b818110156113535760008682815481106112325761123261599b565b6000918252602080832090910154808352600690915260409091205490915060ff1661128a576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006002828154811061129f5761129f61599b565b600091825260209091206005909102018054909150640100000000900473ffffffffffffffffffffffffffffffffffffffff161580156112fc5750600481015460408701516fffffffffffffffffffffffffffffffff9182169116115b1561133e57600181015473ffffffffffffffffffffffffffffffffffffffff16606087015260048101546fffffffffffffffffffffffffffffffff1660408701525b5050808061134b90615a11565b915050611216565b5063ffffffff818116602085810191825260008c81526007909152604090819020865181549351928801517fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000009094169015157fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ff161761010092909416918202939093177fffffffffffffffffffffff00000000000000000000000000000000ffffffffff16650100000000006fffffffffffffffffffffffffffffffff909316929092029190911782556060850151600190920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909316929092179091558490036115d757606083015160008a815260066020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055891580156114d357506000547201000000000000000000000000000000000000900460ff165b156115485760015473ffffffffffffffffffffffffffffffffffffffff166114fb818a612908565b885473ffffffffffffffffffffffffffffffffffffffff909116640100000000027fffffffffffffffff0000000000000000000000000000000000000000ffffffff9091161788556115d5565b61158f73ffffffffffffffffffffffffffffffffffffffff82161561156d5781611589565b600189015473ffffffffffffffffffffffffffffffffffffffff165b89612908565b87547fffffffffffffffff0000000000000000000000000000000000000000ffffffff1664010000000073ffffffffffffffffffffffffffffffffffffffff8316021788555b505b505050505050505050565b600080600054700100000000000000000000000000000000900460ff16600281111561161057611610615625565b14611647576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805260066020527f54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f85460ff166116ab576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff1660026000815481106116d7576116d761599b565b6000918252602090912060059091020154640100000000900473ffffffffffffffffffffffffffffffffffffffff1614611712576001611715565b60025b6000805467ffffffffffffffff421668010000000000000000027fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff82168117835592935083927fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffff000000000000000000ffffffffffffffff909116177001000000000000000000000000000000008360028111156117c6576117c6615625565b0217905560028111156117db576117db615625565b6040517f5e186f09b9c93491f14e277eea7faa5de6a2d4bda75a79af7a3684fbfb42da6090600090a27f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e473ffffffffffffffffffffffffffffffffffffffff1663838c2d1e6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561186c57600080fd5b505af1158015611880573d6000803e3d6000fd5b5050505090565b600560205281600052604060002081815481106118a357600080fd5b90600052602060002001600091509150505481565b905090565b6118ca8383836001611abe565b505050565b6000818152600760209081526040808320600590925282208054825461190090610100900463ffffffff1682615a49565b95945050505050565b60606118b860546020612a09565b73ffffffffffffffffffffffffffffffffffffffff811660009081526003602052604081208054908290559081900361197c576040517f17bfe5f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390527f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6169063f3fef3a390604401600060405180830381600087803b158015611a0c57600080fd5b505af1158015611a20573d6000803e3d6000fd5b5050505060008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114611a7e576040519150601f19603f3d011682016040523d82523d6000602084013e611a83565b606091505b50509050806118ca576040517f83e6cc6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8161480611b3757503373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006925b8704ff96dee942623d6fb5e946ef5884b6316145b611b6d576040517fd386ef3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611b7984848484612a5b565b50505050565b6118ca8383836000611abe565b3273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c81614611bfb576040517fd386ef3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c036133fc565b565b600080600054700100000000000000000000000000000000900460ff166002811115611c3357611c33615625565b14611c6a576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028381548110611c7f57611c7f61599b565b600091825260208220600590910201805490925063ffffffff90811614611cee57815460028054909163ffffffff16908110611cbd57611cbd61599b565b906000526020600020906005020160040160109054906101000a90046fffffffffffffffffffffffffffffffff1690505b6004820154600090611d2690700100000000000000000000000000000000900467ffffffffffffffff165b67ffffffffffffffff1690565b611d3a9067ffffffffffffffff1642615a49565b611d59611d19846fffffffffffffffffffffffffffffffff1660401c90565b67ffffffffffffffff16611d6d91906159f9565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b01667ffffffffffffffff168167ffffffffffffffff1611611dba5780611900565b7f00000000000000000000000000000000000000000000000000000000000004b095945050505050565b600080611e83836fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690507f0000000000000000000000000000000000000000000000000000000000000008811115611ee2576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b642e90edd00062061a806311e1a3006000611efd8383615a8f565b9050670de0b6b3a76400006000611f34827f0000000000000000000000000000000000000000000000000000000000000008615aa3565b90506000611f52611f4d670de0b6b3a764000086615aa3565b613955565b90506000611f608484613bb0565b90506000611f6e8383613bff565b90506000611f7b82613c2d565b90506000611f9a82611f95670de0b6b3a76400008f615aa3565b613e15565b90506000611fa88b83613bff565b9050611fb4818d615aa3565b9f9e505050505050505050505050505050565b60028181548110611fd757600080fd5b60009182526020909120600590910201805460018201546002830154600384015460049094015463ffffffff8416955064010000000090930473ffffffffffffffffffffffffffffffffffffffff908116949216926fffffffffffffffffffffffffffffffff91821692918082169170010000000000000000000000000000000090041687565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c81614806120d757503373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006925b8704ff96dee942623d6fb5e946ef5884b6316145b61210d576040517fd386ef3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61211b868686868686613e4f565b505050505050565b60008054700100000000000000000000000000000000900460ff16600281111561214f5761214f615625565b14612186576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806000806121958661447e565b935093509350935060006121ab85858585614887565b905060007f00000000000000000000000028bf1582225713139c0e898326db808b6484cfd473ffffffffffffffffffffffffffffffffffffffff16637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561221a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061223e9190615ae0565b9050600189036123365773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a8461229a367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036034013590565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b16815260048101939093526024830191909152604482015260206064820152608481018a905260a4015b6020604051808303816000875af115801561230c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123309190615afd565b506115d7565b600289036123625773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a848961229a565b6003890361238e5773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a848761229a565b600489036124aa5760006123d46fffffffffffffffffffffffffffffffff85167f0000000000000000000000000000000000000000000000000000000000000004614941565b6009546123e191906159f9565b6123ec9060016159f9565b905073ffffffffffffffffffffffffffffffffffffffff82166352f0f3ad8b8560405160e084901b7fffffffff000000000000000000000000000000000000000000000000000000001681526004810192909252602482015260c084901b604482015260086064820152608481018b905260a4016020604051808303816000875af115801561247f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124a39190615afd565b50506115d7565b60058903612543576040517f52f0f3ad000000000000000000000000000000000000000000000000000000008152600481018a9052602481018390527f000000000000000000000000000000000000000000000000000000000000038560c01b6044820152600860648201526084810188905273ffffffffffffffffffffffffffffffffffffffff8216906352f0f3ad9060a4016122ed565b6040517fff137e6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000001367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013560606125ce611909565b9050909192565b60008160000151826020015183604001518460600151604051602001612614949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b60408051808201909152600080825260208201528151600003612680576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b606060008060006126ae856149ef565b9194509250905060018160018111156126c9576126c9615625565b14612700576040517f4b9c6abe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b845161270c83856159f9565b14612743576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516020808252610420820190925290816020015b604080518082019091526000808252602082015281526020019060019003908161275a5790505093506000835b8651811015612848576000806127cd6040518060400160405280858c600001516127b19190615a49565b8152602001858c602001516127c691906159f9565b90526149ef565b5091509150604051806040016040528083836127e991906159f9565b8152602001848b602001516127fe91906159f9565b8152508885815181106128135761281361599b565b60209081029190910101526128296001856159f9565b935061283581836159f9565b61283f90846159f9565b92505050612787565b50845250919392505050565b60606000806000612864856149ef565b91945092509050600081600181111561287f5761287f615625565b146128b6576040517f1ff9b2e400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6128c082846159f9565b8551146128f9576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61190085602001518484614e8d565b600281015473ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080546fffffffffffffffffffffffffffffffff909316928392906129579084906159f9565b90915550506040517f7eee288d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152602482018390527f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa61690637eee288d90604401600060405180830381600087803b1580156129ec57600080fd5b505af1158015612a00573d6000803e3d6000fd5b50505050505050565b604051818152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90038284820160208401378260208301016000815260208101604052505092915050565b60008054700100000000000000000000000000000000900460ff166002811115612a8757612a87615625565b14612abe576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028481548110612ad357612ad361599b565b60009182526020918290206040805160e0810182526005909302909101805463ffffffff8116845273ffffffffffffffffffffffffffffffffffffffff64010000000090910481169484019490945260018101549093169082015260028201546fffffffffffffffffffffffffffffffff908116606083015260038301546080830181905260049093015480821660a084015270010000000000000000000000000000000090041660c082015291508514612bba576040517f3014033200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60a0810151600083156fffffffffffffffffffffffffffffffff83161760011b90506000612c7a826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050861580612cb55750612cb27f000000000000000000000000000000000000000000000000000000000000000460026159f9565b81145b8015612cbf575084155b15612cf6576040517fa42637bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff168015612d1c575086155b15612d53576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000008811115612dad576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612dd87f000000000000000000000000000000000000000000000000000000000000000460016159f9565b8103612dea57612dea86888588614f22565b34612df483611de4565b14612e2b576040517f8620aa1900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612e3688611c05565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b0811690821603612e9e576040517f3381d11400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001667ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000004b016612efe9190615b16565b67ffffffffffffffff16612f198267ffffffffffffffff1690565b67ffffffffffffffff161115612ffb576000612f5660017f0000000000000000000000000000000000000000000000000000000000000004615a49565b8314612f8c5767ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016612fc1565b612fc17f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff166002615b3f565b9050612ff7817f00000000000000000000000000000000000000000000000000000000000004b067ffffffffffffffff16615b16565b9150505b6000604082901b42176000898152608086901b6fffffffffffffffffffffffffffffffff8c1617602052604081209192509060008181526004602052604090205490915060ff1615613079576040517f80497e3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60016004600083815260200190815260200160002060006101000a81548160ff02191690831515021790555060026040518060e001604052808c63ffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020013373ffffffffffffffffffffffffffffffffffffffff168152602001346fffffffffffffffffffffffffffffffff1681526020018b8152602001876fffffffffffffffffffffffffffffffff168152602001846fffffffffffffffffffffffffffffffff16815250908060018154018082558091505060019003906000526020600020906005020160009091909190915060008201518160000160006101000a81548163ffffffff021916908363ffffffff16021790555060208201518160000160046101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060608201518160020160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506080820151816003015560a08201518160040160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060c08201518160040160106101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505050600560008b8152602001908152602001600020600160028054905061330f9190615a49565b81546001810183556000928352602083200155604080517fd0e30db0000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa6169263d0e30db09234926004808301939282900301818588803b1580156133a757600080fd5b505af11580156133bb573d6000803e3d6000fd5b50506040513393508c92508d91507f9b3245740ec3b155098a55be84957a4da13eaf7f14a8bc6f53126c0b9350f2be90600090a45050505050505050505050565b60005471010000000000000000000000000000000000900460ff161561344e576040517f0dc149f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f7258a80700000000000000000000000000000000000000000000000000000000815263ffffffff7f0000000000000000000000000000000000000000000000000000000000000001166004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001c23a6d89f95ef3148bcda8e242cab145bf9c0e41690637258a807906024016040805180830381865afa158015613502573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135269190615b6f565b909250905081613562576040517f6a6bc3b200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080518082019091528281526020018190526008829055600981905536607a1461359557639824bdab6000526004601cfd5b80367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401351161362f576040517ff40239db000000000000000000000000000000000000000000000000000000008152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013560048201526024015b60405180910390fd5b6040805160e08101825263ffffffff8082526000602083018181527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe369081013560f01c90038035606090811c868801908152346fffffffffffffffffffffffffffffffff81811693890193845260149094013560808901908152600160a08a0181815242871660c08c019081526002805493840181558a529a5160059092027f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace81018054995173ffffffffffffffffffffffffffffffffffffffff908116640100000000027fffffffffffffffff000000000000000000000000000000000000000000000000909b1694909c16939093179890981790915592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf87018054918a167fffffffffffffffffffffffff000000000000000000000000000000000000000090921691909117905592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad0860180549186167fffffffffffffffffffffffffffffffff0000000000000000000000000000000090921691909117905591517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad185015551955182167001000000000000000000000000000000000295909116949094177f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad29091015580547fffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffff167101000000000000000000000000000000000017815583517fd0e30db000000000000000000000000000000000000000000000000000000000815293517f0000000000000000000000000c8b5822b6e02cda722174f19a1439a7495a3fa69092169363d0e30db093926004828101939282900301818588803b15801561390457600080fd5b505af1158015613918573d6000803e3d6000fd5b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000164267ffffffffffffffff161790555050505050565b6fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff1060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b17600082136139b457631615e6386000526004601cfd5b7ff8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff6f8421084210842108cc6318c6db6d54be83831c1c601f161a1890811b609f90811c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506029190037d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b302017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d90565b60007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218311670de0b6b3a764000002158202613bed57637c5f487d6000526004601cfd5b50670de0b6b3a7640000919091020490565b600081600019048311820215613c1d5763bac65e5b6000526004601cfd5b50670de0b6b3a764000091020490565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdc0d0570925a462d78213613c5b57919050565b680755bf798b4a1bf1e58212613c795763a37bfec96000526004601cfd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b6000613e46670de0b6b3a764000083613e2d86613955565b613e379190615b93565b613e419190615c4f565b613c2d565b90505b92915050565b60008054700100000000000000000000000000000000900460ff166002811115613e7b57613e7b615625565b14613eb2576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028781548110613ec757613ec761599b565b6000918252602082206005919091020160048101549092506fffffffffffffffffffffffffffffffff16908715821760011b9050613f267f000000000000000000000000000000000000000000000000000000000000000860016159f9565b613fc2826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1614613ffc576040517f5f53dd9800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008089156140f35761404f7f00000000000000000000000000000000000000000000000000000000000000047f0000000000000000000000000000000000000000000000000000000000000008615a49565b6001901b61406e846fffffffffffffffffffffffffffffffff166150d3565b6fffffffffffffffffffffffffffffffff1661408a9190615cb7565b156140c7576140be6140af60016fffffffffffffffffffffffffffffffff8716615ccb565b865463ffffffff166000615172565b600301546140e9565b7f00000000000000000000000000000000000000000000000000000000000000005b915084905061411d565b6003850154915061411a6140af6fffffffffffffffffffffffffffffffff86166001615cf4565b90505b600882901b60088a8a60405161413492919061598b565b6040518091039020901b14614175576040517f696550ff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006141808c615256565b9050600061418f836003015490565b6040517fe14ced320000000000000000000000000000000000000000000000000000000081527f00000000000000000000000028bf1582225713139c0e898326db808b6484cfd473ffffffffffffffffffffffffffffffffffffffff169063e14ced3290614209908f908f908f908f908a90600401615d71565b6020604051808303816000875af1158015614228573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061424c9190615afd565b6004850154911491506000906002906142f7906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b614393896fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b61439d9190615dab565b6143a79190615dce565b60ff1615905081151581036143e8576040517ffb4e40dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8754640100000000900473ffffffffffffffffffffffffffffffffffffffff161561443f576040517f9071e6af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505085547fffffffffffffffff0000000000000000000000000000000000000000ffffffff163364010000000002179095555050505050505050505050565b600080600080600085905060006002828154811061449e5761449e61599b565b600091825260209091206004600590920201908101549091507f000000000000000000000000000000000000000000000000000000000000000490614575906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff16116145af576040517fb34b5c2200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000815b60048301547f000000000000000000000000000000000000000000000000000000000000000490614676906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1692508211156146eb57825463ffffffff166146b57f000000000000000000000000000000000000000000000000000000000000000460016159f9565b83036146bf578391505b600281815481106146d2576146d261599b565b90600052602060002090600502019350809450506145b3565b600481810154908401546fffffffffffffffffffffffffffffffff91821691166000816fffffffffffffffffffffffffffffffff1661475461473f856fffffffffffffffffffffffffffffffff1660011c90565b6fffffffffffffffffffffffffffffffff1690565b6fffffffffffffffffffffffffffffffff16149050801561482357600061478c836fffffffffffffffffffffffffffffffff166150d3565b6fffffffffffffffffffffffffffffffff1611156147f75760006147ce6147c660016fffffffffffffffffffffffffffffffff8616615ccb565b896001615172565b6003810154600490910154909c506fffffffffffffffffffffffffffffffff169a506147fd9050565b6008549a505b600386015460048701549099506fffffffffffffffffffffffffffffffff169750614879565b60006148456147c66fffffffffffffffffffffffffffffffff85166001615cf4565b6003808901546004808b015492840154930154909e506fffffffffffffffffffffffffffffffff9182169d50919b50169850505b505050505050509193509193565b60006fffffffffffffffffffffffffffffffff8416156148f45760408051602081018790526fffffffffffffffffffffffffffffffff8087169282019290925260608101859052908316608082015260a00160405160208183030381529060405280519060200120611900565b82826040516020016149229291909182526fffffffffffffffffffffffffffffffff16602082015260400190565b6040516020818303038152906040528051906020012095945050505050565b6000806149ce847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690508083036001841b600180831b0386831b17039250505092915050565b60008060008360000151600003614a32576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020840151805160001a607f8111614a57576000600160009450945094505050614e86565b60b78111614b6d576000614a6c608083615a49565b905080876000015111614aab576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001838101517fff00000000000000000000000000000000000000000000000000000000000000169082148015614b2357507f80000000000000000000000000000000000000000000000000000000000000007fff000000000000000000000000000000000000000000000000000000000000008216105b15614b5a576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060019550935060009250614e86915050565b60bf8111614ccb576000614b8260b783615a49565b905080876000015111614bc1576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614c23576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614c6b576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614c7581846159f9565b895111614cae576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614cb98360016159f9565b9750955060009450614e869350505050565b60f78111614d30576000614ce060c083615a49565b905080876000015111614d1f576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600195509350849250614e86915050565b6000614d3d60f783615a49565b905080876000015111614d7c576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614dde576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614e26576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614e3081846159f9565b895111614e69576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614e748360016159f9565b9750955060019450614e869350505050565b9193909250565b60608167ffffffffffffffff811115614ea857614ea86158cf565b6040519080825280601f01601f191660200182016040528015614ed2576020820181803683370190505b5090508115614f1b576000614ee784866159f9565b90506020820160005b84811015614f08578281015182820152602001614ef0565b84811115614f17576000858301525b5050505b9392505050565b6000614f416fffffffffffffffffffffffffffffffff84166001615cf4565b90506000614f5182866001615172565b9050600086901a838061503d5750614f8a60027f0000000000000000000000000000000000000000000000000000000000000004615cb7565b600483015460029061502e906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b6150389190615dce565b60ff16145b156150955760ff811660011480615057575060ff81166002145b615090576040517ff40239db00000000000000000000000000000000000000000000000000000000815260048101889052602401613626565b612a00565b60ff811615612a00576040517ff40239db00000000000000000000000000000000000000000000000000000000815260048101889052602401613626565b600080615160837e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b600160ff919091161b90920392915050565b600080826151bb576151b66fffffffffffffffffffffffffffffffff86167f0000000000000000000000000000000000000000000000000000000000000004615285565b6151d6565b6151d6856fffffffffffffffffffffffffffffffff16615411565b9050600284815481106151eb576151eb61599b565b906000526020600020906005020191505b60048201546fffffffffffffffffffffffffffffffff82811691161461524e57815460028054909163ffffffff169081106152395761523961599b565b906000526020600020906005020191506151fc565b509392505050565b60008060008060006152678661447e565b935093509350935061527b84848484614887565b9695505050505050565b600081615324846fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff161161533a5763b34b5c226000526004601cfd5b61534383615411565b9050816153e2826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1611613e4957613e466153f88360016159f9565b6fffffffffffffffffffffffffffffffff8316906154b6565b600081196001830116816154a5827e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169390931c8015179392505050565b600080615543847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050808303600180821b0385821b179250505092915050565b60008083601f84011261557157600080fd5b50813567ffffffffffffffff81111561558957600080fd5b6020830191508360208285010111156155a157600080fd5b9250929050565b600080600083850360a08112156155be57600080fd5b60808112156155cc57600080fd5b50839250608084013567ffffffffffffffff8111156155ea57600080fd5b6155f68682870161555f565b9497909650939450505050565b6000806040838503121561561657600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b602081016003831061568f577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b6000806000606084860312156156aa57600080fd5b505081359360208301359350604090920135919050565b6000815180845260005b818110156156e7576020818501810151868301820152016156cb565b818111156156f9576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000613e4660208301846156c1565b60006020828403121561575157600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff8116811461577a57600080fd5b50565b60006020828403121561578f57600080fd5b8135614f1b81615758565b803580151581146157aa57600080fd5b919050565b600080600080608085870312156157c557600080fd5b8435935060208501359250604085013591506157e36060860161579a565b905092959194509250565b60006020828403121561580057600080fd5b81356fffffffffffffffffffffffffffffffff81168114614f1b57600080fd5b6000806000806000806080878903121561583957600080fd5b863595506158496020880161579a565b9450604087013567ffffffffffffffff8082111561586657600080fd5b6158728a838b0161555f565b9096509450606089013591508082111561588b57600080fd5b5061589889828a0161555f565b979a9699509497509295939492505050565b63ffffffff8416815282602082015260606040820152600061190060608301846156c1565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006080828403121561591057600080fd5b6040516080810181811067ffffffffffffffff8211171561595a577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b8183823760009101908152919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115615a0c57615a0c6159ca565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203615a4257615a426159ca565b5060010190565b600082821015615a5b57615a5b6159ca565b500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082615a9e57615a9e615a60565b500490565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615615adb57615adb6159ca565b500290565b600060208284031215615af257600080fd5b8151614f1b81615758565b600060208284031215615b0f57600080fd5b5051919050565b600067ffffffffffffffff83811690831681811015615b3757615b376159ca565b039392505050565b600067ffffffffffffffff80831681851681830481118215151615615b6657615b666159ca565b02949350505050565b60008060408385031215615b8257600080fd5b505080516020909101519092909150565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615615bd457615bd46159ca565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615615c0f57615c0f6159ca565b60008712925087820587128484161615615c2b57615c2b6159ca565b87850587128184161615615c4157615c416159ca565b505050929093029392505050565b600082615c5e57615c5e615a60565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615615cb257615cb26159ca565b500590565b600082615cc657615cc6615a60565b500690565b60006fffffffffffffffffffffffffffffffff83811690831681811015615b3757615b376159ca565b60006fffffffffffffffffffffffffffffffff808316818516808303821115615d1f57615d1f6159ca565b01949350505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b606081526000615d85606083018789615d28565b8281036020840152615d98818688615d28565b9150508260408301529695505050505050565b600060ff821660ff841680821015615dc557615dc56159ca565b90039392505050565b600060ff831680615de157615de1615a60565b8060ff8416069150509291505056fea164736f6c634300080f000a"; }
diff --git OP/packages/contracts-bedrock/test/kontrol/scripts/json/clean_json.py CELO/packages/contracts-bedrock/test/kontrol/scripts/json/clean_json.py index 570ecef5d7aec924a3f9b9e137aabf5acba39c90..58746feb3d045e3a9537edd72d63f7895a26296e 100644 --- OP/packages/contracts-bedrock/test/kontrol/scripts/json/clean_json.py +++ CELO/packages/contracts-bedrock/test/kontrol/scripts/json/clean_json.py @@ -1,7 +1,7 @@ """ Description: Unescapes the JSON produced by the stateDiff modifier - defined in contracts-bedrock/scripts/Deploy.s.sol + defined in contracts-bedrock/scripts/deploy/Deploy.s.sol This script is used in ../make-summary-deployment.sh   Usage:
diff --git OP/packages/contracts-bedrock/test/kontrol/scripts/make-summary-deployment.sh CELO/packages/contracts-bedrock/test/kontrol/scripts/make-summary-deployment.sh index 40c7fa5778bd59c6162a1cbc1ba578955c9760cb..8ed7c1cf719823cc7ec4836b95c28a5da0dc9fee 100755 --- OP/packages/contracts-bedrock/test/kontrol/scripts/make-summary-deployment.sh +++ CELO/packages/contracts-bedrock/test/kontrol/scripts/make-summary-deployment.sh @@ -53,7 +53,7 @@ if [ ! -f "snapshots/state-diff/Deploy.json" ]; then touch snapshots/state-diff/Deploy.json; fi   -DEPLOY_SCRIPT="./scripts/Deploy.s.sol" +DEPLOY_SCRIPT="./scripts/deploy/Deploy.s.sol" conditionally_start_docker   # Create a backup
diff --git OP/packages/devnet-tasks/tsconfig.json CELO/packages/devnet-tasks/tsconfig.json index c59f2e54c42bdc296ea6601562dc24b807f21516..35378d1259353f3275444c29659a2e64a1db907f 100644 --- OP/packages/devnet-tasks/tsconfig.json +++ CELO/packages/devnet-tasks/tsconfig.json @@ -1,10 +1,29 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { "lib": ["ES2021"], "rootDir": "./src", - "outDir": "./dist" + "outDir": "./dist", + "skipLibCheck": true, + "module": "commonjs", + "target": "es2017", + "sourceMap": true, + "esModuleInterop": true, + "composite": true, + "resolveJsonModule": true, + "declaration": true, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "typeRoots": [ + "node_modules/@types" + ] }, + "exclude": [ + "node_modules", + "dist" + ], "include": [ "src/**/*", "src/forge-artifacts/*.json"
diff --git OP/pnpm-workspace.yaml CELO/pnpm-workspace.yaml index a3cef3117ea0245740f8eee44e3ffeef72037003..18ec407efca710b5d0d1e19dd3e34a27ac6716a1 100644 --- OP/pnpm-workspace.yaml +++ CELO/pnpm-workspace.yaml @@ -1,3 +1,2 @@ packages: - 'packages/*' - - 'indexer/api-ts'
diff --git OP/proxyd/cmd/proxyd/main.go CELO/proxyd/cmd/proxyd/main.go deleted file mode 100644 index 3daa80b38710a22f49b335f5f8966ee897d64252..0000000000000000000000000000000000000000 --- OP/proxyd/cmd/proxyd/main.go +++ /dev/null @@ -1,121 +0,0 @@ -package main - -import ( - "fmt" - "net" - "net/http" - "net/http/pprof" - "os" - "os/signal" - "strconv" - "strings" - "syscall" - - "github.com/BurntSushi/toml" - "golang.org/x/exp/slog" - - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/proxyd" -) - -var ( - GitVersion = "" - GitCommit = "" - GitDate = "" -) - -func main() { - // Set up logger with a default INFO level in case we fail to parse flags. - // Otherwise the final critical log won't show what the parsing error was. - proxyd.SetLogLevel(slog.LevelInfo) - - log.Info("starting proxyd", "version", GitVersion, "commit", GitCommit, "date", GitDate) - - if len(os.Args) < 2 { - log.Crit("must specify a config file on the command line") - } - - config := new(proxyd.Config) - if _, err := toml.DecodeFile(os.Args[1], config); err != nil { - log.Crit("error reading config file", "err", err) - } - - // update log level from config - logLevel, err := LevelFromString(config.Server.LogLevel) - if err != nil { - logLevel = log.LevelInfo - if config.Server.LogLevel != "" { - log.Warn("invalid server.log_level set: " + config.Server.LogLevel) - } - } - proxyd.SetLogLevel(logLevel) - - if config.Server.EnablePprof { - log.Info("starting pprof", "addr", "0.0.0.0", "port", "6060") - pprofSrv := StartPProf("0.0.0.0", 6060) - log.Info("started pprof server", "addr", pprofSrv.Addr) - defer func() { - if err := pprofSrv.Close(); err != nil { - log.Error("failed to stop pprof server", "err", err) - } - }() - } - - _, shutdown, err := proxyd.Start(config) - if err != nil { - log.Crit("error starting proxyd", "err", err) - } - - sig := make(chan os.Signal, 1) - signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) - recvSig := <-sig - log.Info("caught signal, shutting down", "signal", recvSig) - shutdown() -} - -// LevelFromString returns the appropriate Level from a string name. -// Useful for parsing command line args and configuration files. -// It also converts strings to lowercase. -// Note: copied from op-service/log to avoid monorepo dependency -func LevelFromString(lvlString string) (slog.Level, error) { - lvlString = strings.ToLower(lvlString) // ignore case - switch lvlString { - case "trace", "trce": - return log.LevelTrace, nil - case "debug", "dbug": - return log.LevelDebug, nil - case "info": - return log.LevelInfo, nil - case "warn": - return log.LevelWarn, nil - case "error", "eror": - return log.LevelError, nil - case "crit": - return log.LevelCrit, nil - default: - return log.LevelDebug, fmt.Errorf("unknown level: %v", lvlString) - } -} - -func StartPProf(hostname string, port int) *http.Server { - mux := http.NewServeMux() - - // have to do below to support multiple servers, since the - // pprof import only uses DefaultServeMux - mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) - mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) - mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) - mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) - mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) - - addr := net.JoinHostPort(hostname, strconv.Itoa(port)) - srv := &http.Server{ - Handler: mux, - Addr: addr, - } - - go srv.ListenAndServe() - - return srv -}
diff --git OP/proxyd/integration_tests/batch_timeout_test.go CELO/proxyd/integration_tests/batch_timeout_test.go deleted file mode 100644 index 4906c1d07e66a9df8d8caa9110708ddb3750cd92..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/batch_timeout_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package integration_tests - -import ( - "net/http" - "os" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" -) - -const ( - batchTimeoutResponse = `{"error":{"code":-32015,"message":"gateway timeout"},"id":null,"jsonrpc":"2.0"}` -) - -func TestBatchTimeout(t *testing.T) { - slowBackend := NewMockBackend(nil) - defer slowBackend.Close() - - require.NoError(t, os.Setenv("SLOW_BACKEND_RPC_URL", slowBackend.URL())) - - config := ReadConfig("batch_timeout") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - slowBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // check the config. The sleep duration should be at least double the server.timeout_seconds config to prevent flakes - time.Sleep(time.Second * 2) - BatchedResponseHandler(200, goodResponse)(w, r) - })) - res, statusCode, err := client.SendBatchRPC( - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("1", "eth_chainId", nil), - ) - require.NoError(t, err) - require.Equal(t, 504, statusCode) - RequireEqualJSON(t, []byte(batchTimeoutResponse), res) - require.Equal(t, 1, len(slowBackend.Requests())) -}
diff --git OP/proxyd/integration_tests/batching_test.go CELO/proxyd/integration_tests/batching_test.go deleted file mode 100644 index c1f8b386d09a55acbc538073c25b6164436c5f66..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/batching_test.go +++ /dev/null @@ -1,188 +0,0 @@ -package integration_tests - -import ( - "net/http" - "os" - "testing" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" -) - -func TestBatching(t *testing.T) { - config := ReadConfig("batching") - - chainIDResponse1 := `{"jsonrpc": "2.0", "result": "hello1", "id": 1}` - chainIDResponse2 := `{"jsonrpc": "2.0", "result": "hello2", "id": 2}` - chainIDResponse3 := `{"jsonrpc": "2.0", "result": "hello3", "id": 3}` - netVersionResponse1 := `{"jsonrpc": "2.0", "result": "1.0", "id": 1}` - callResponse1 := `{"jsonrpc": "2.0", "result": "ekans1", "id": 1}` - - ethAccountsResponse2 := `{"jsonrpc": "2.0", "result": [], "id": 2}` - - backendResTooLargeResponse1 := `{"error":{"code":-32020,"message":"backend response too large"},"id":1,"jsonrpc":"2.0"}` - backendResTooLargeResponse2 := `{"error":{"code":-32020,"message":"backend response too large"},"id":2,"jsonrpc":"2.0"}` - - type mockResult struct { - method string - id string - result interface{} - } - - chainIDMock1 := mockResult{"eth_chainId", "1", "hello1"} - chainIDMock2 := mockResult{"eth_chainId", "2", "hello2"} - chainIDMock3 := mockResult{"eth_chainId", "3", "hello3"} - netVersionMock1 := mockResult{"net_version", "1", "1.0"} - callMock1 := mockResult{"eth_call", "1", "ekans1"} - - tests := []struct { - name string - handler http.Handler - mocks []mockResult - reqs []*proxyd.RPCReq - expectedRes string - maxUpstreamBatchSize int - numExpectedForwards int - maxResponseSizeBytes int64 - }{ - { - name: "backend returns batches out of order", - mocks: []mockResult{chainIDMock1, chainIDMock2, chainIDMock3}, - reqs: []*proxyd.RPCReq{ - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("2", "eth_chainId", nil), - NewRPCReq("3", "eth_chainId", nil), - }, - expectedRes: asArray(chainIDResponse1, chainIDResponse2, chainIDResponse3), - maxUpstreamBatchSize: 2, - numExpectedForwards: 2, - }, - { - // infura behavior - name: "backend returns single RPC response object as error", - handler: SingleResponseHandler(500, `{"jsonrpc":"2.0","error":{"code":-32001,"message":"internal server error"},"id":1}`), - reqs: []*proxyd.RPCReq{ - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("2", "eth_chainId", nil), - }, - expectedRes: asArray( - `{"error":{"code":-32011,"message":"no backends available for method"},"id":1,"jsonrpc":"2.0"}`, - `{"error":{"code":-32011,"message":"no backends available for method"},"id":2,"jsonrpc":"2.0"}`, - ), - maxUpstreamBatchSize: 10, - numExpectedForwards: 1, - }, - { - name: "backend returns single RPC response object for minibatches", - handler: SingleResponseHandler(500, `{"jsonrpc":"2.0","error":{"code":-32001,"message":"internal server error"},"id":1}`), - reqs: []*proxyd.RPCReq{ - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("2", "eth_chainId", nil), - }, - expectedRes: asArray( - `{"error":{"code":-32011,"message":"no backends available for method"},"id":1,"jsonrpc":"2.0"}`, - `{"error":{"code":-32011,"message":"no backends available for method"},"id":2,"jsonrpc":"2.0"}`, - ), - maxUpstreamBatchSize: 1, - numExpectedForwards: 2, - }, - { - name: "duplicate request ids are on distinct batches", - mocks: []mockResult{ - netVersionMock1, - chainIDMock2, - chainIDMock1, - callMock1, - }, - reqs: []*proxyd.RPCReq{ - NewRPCReq("1", "net_version", nil), - NewRPCReq("2", "eth_chainId", nil), - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("1", "eth_call", nil), - }, - expectedRes: asArray(netVersionResponse1, chainIDResponse2, chainIDResponse1, callResponse1), - maxUpstreamBatchSize: 2, - numExpectedForwards: 3, - }, - { - name: "over max size", - mocks: []mockResult{}, - reqs: []*proxyd.RPCReq{ - NewRPCReq("1", "net_version", nil), - NewRPCReq("2", "eth_chainId", nil), - NewRPCReq("3", "eth_chainId", nil), - NewRPCReq("4", "eth_call", nil), - NewRPCReq("5", "eth_call", nil), - NewRPCReq("6", "eth_call", nil), - }, - expectedRes: "{\"error\":{\"code\":-32014,\"message\":\"over batch size custom message\"},\"id\":null,\"jsonrpc\":\"2.0\"}", - maxUpstreamBatchSize: 2, - numExpectedForwards: 0, - }, - { - name: "eth_accounts does not get forwarded", - mocks: []mockResult{ - callMock1, - }, - reqs: []*proxyd.RPCReq{ - NewRPCReq("1", "eth_call", nil), - NewRPCReq("2", "eth_accounts", nil), - }, - expectedRes: asArray(callResponse1, ethAccountsResponse2), - maxUpstreamBatchSize: 2, - numExpectedForwards: 1, - }, - { - name: "large upstream response gets dropped", - mocks: []mockResult{chainIDMock1, chainIDMock2}, - reqs: []*proxyd.RPCReq{ - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("2", "eth_chainId", nil), - }, - expectedRes: asArray(backendResTooLargeResponse1, backendResTooLargeResponse2), - maxUpstreamBatchSize: 2, - numExpectedForwards: 1, - maxResponseSizeBytes: 1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config.Server.MaxUpstreamBatchSize = tt.maxUpstreamBatchSize - config.BackendOptions.MaxResponseSizeBytes = tt.maxResponseSizeBytes - - handler := tt.handler - if handler == nil { - router := NewBatchRPCResponseRouter() - for _, mock := range tt.mocks { - router.SetRoute(mock.method, mock.id, mock.result) - } - handler = router - } - - goodBackend := NewMockBackend(handler) - defer goodBackend.Close() - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - res, statusCode, err := client.SendBatchRPC(tt.reqs...) - require.NoError(t, err) - require.Equal(t, http.StatusOK, statusCode) - RequireEqualJSON(t, []byte(tt.expectedRes), res) - - if tt.numExpectedForwards != 0 { - require.Equal(t, tt.numExpectedForwards, len(goodBackend.Requests())) - } - - if handler, ok := handler.(*BatchRPCResponseRouter); ok { - for i, mock := range tt.mocks { - require.Equal(t, 1, handler.GetNumCalls(mock.method, mock.id), i) - } - } - }) - } -}
diff --git OP/proxyd/integration_tests/caching_test.go CELO/proxyd/integration_tests/caching_test.go deleted file mode 100644 index e74b85b4a7bf115d3059a858d36c6445475d5462..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/caching_test.go +++ /dev/null @@ -1,275 +0,0 @@ -package integration_tests - -import ( - "bytes" - "fmt" - "os" - "testing" - "time" - - "github.com/alicebob/miniredis" - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" -) - -func TestCaching(t *testing.T) { - redis, err := miniredis.Run() - require.NoError(t, err) - defer redis.Close() - - hdlr := NewBatchRPCResponseRouter() - /* cacheable */ - hdlr.SetRoute("eth_chainId", "999", "0x420") - hdlr.SetRoute("net_version", "999", "0x1234") - hdlr.SetRoute("eth_getBlockTransactionCountByHash", "999", "eth_getBlockTransactionCountByHash") - hdlr.SetRoute("eth_getBlockByHash", "999", "eth_getBlockByHash") - hdlr.SetRoute("eth_getTransactionByHash", "999", "eth_getTransactionByHash") - hdlr.SetRoute("eth_getTransactionByBlockHashAndIndex", "999", "eth_getTransactionByBlockHashAndIndex") - hdlr.SetRoute("eth_getUncleByBlockHashAndIndex", "999", "eth_getUncleByBlockHashAndIndex") - hdlr.SetRoute("eth_getTransactionReceipt", "999", "eth_getTransactionReceipt") - hdlr.SetRoute("debug_getRawReceipts", "999", "debug_getRawReceipts") - /* not cacheable */ - hdlr.SetRoute("eth_getBlockByNumber", "999", "eth_getBlockByNumber") - hdlr.SetRoute("eth_blockNumber", "999", "eth_blockNumber") - hdlr.SetRoute("eth_call", "999", "eth_call") - - backend := NewMockBackend(hdlr) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - require.NoError(t, os.Setenv("REDIS_URL", fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()))) - config := ReadConfig("caching") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - // allow time for the block number fetcher to fire - time.Sleep(1500 * time.Millisecond) - - tests := []struct { - method string - params []interface{} - response string - backendCalls int - }{ - /* cacheable */ - { - "eth_chainId", - nil, - "{\"jsonrpc\": \"2.0\", \"result\": \"0x420\", \"id\": 999}", - 1, - }, - { - "net_version", - nil, - "{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 999}", - 1, - }, - { - "eth_getBlockTransactionCountByHash", - []interface{}{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockTransactionCountByHash\", \"id\": 999}", - 1, - }, - { - "eth_getBlockByHash", - []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByHash\", \"id\": 999}", - 1, - }, - { - "eth_getTransactionByBlockHashAndIndex", - []interface{}{"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", "0x55"}, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionByBlockHashAndIndex\", \"id\": 999}", - 1, - }, - { - "eth_getUncleByBlockHashAndIndex", - []interface{}{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238", "0x90"}, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getUncleByBlockHashAndIndex\", \"id\": 999}", - 1, - }, - /* not cacheable */ - { - "eth_getBlockByNumber", - []interface{}{ - "0x1", - true, - }, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByNumber\", \"id\": 999}", - 2, - }, - { - "eth_getTransactionReceipt", - []interface{}{"0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5"}, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionReceipt\", \"id\": 999}", - 2, - }, - { - "eth_getTransactionByHash", - []interface{}{"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"}, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionByHash\", \"id\": 999}", - 2, - }, - { - "eth_call", - []interface{}{ - struct { - To string `json:"to"` - }{ - "0x1234", - }, - "0x60", - }, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", - 2, - }, - { - "eth_blockNumber", - nil, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_blockNumber\", \"id\": 999}", - 2, - }, - { - "eth_call", - []interface{}{ - struct { - To string `json:"to"` - }{ - "0x1234", - }, - "latest", - }, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", - 2, - }, - { - "eth_call", - []interface{}{ - struct { - To string `json:"to"` - }{ - "0x1234", - }, - "pending", - }, - "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", - 2, - }, - } - for _, tt := range tests { - t.Run(tt.method, func(t *testing.T) { - resRaw, _, err := client.SendRPC(tt.method, tt.params) - require.NoError(t, err) - resCache, _, err := client.SendRPC(tt.method, tt.params) - require.NoError(t, err) - RequireEqualJSON(t, []byte(tt.response), resCache) - RequireEqualJSON(t, resRaw, resCache) - require.Equal(t, tt.backendCalls, countRequests(backend, tt.method)) - backend.Reset() - }) - } - - t.Run("nil responses should not be cached", func(t *testing.T) { - hdlr.SetRoute("eth_getBlockByHash", "999", nil) - resRaw, _, err := client.SendRPC("eth_getBlockByHash", []interface{}{"0x123"}) - require.NoError(t, err) - resCache, _, err := client.SendRPC("eth_getBlockByHash", []interface{}{"0x123"}) - require.NoError(t, err) - RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":null}"), resRaw) - RequireEqualJSON(t, resRaw, resCache) - require.Equal(t, 2, countRequests(backend, "eth_getBlockByHash")) - }) - - t.Run("debug_getRawReceipts with 0 receipts should not be cached", func(t *testing.T) { - backend.Reset() - hdlr.SetRoute("debug_getRawReceipts", "999", []string{}) - resRaw, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff"}) - require.NoError(t, err) - resCache, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff"}) - require.NoError(t, err) - RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":[]}"), resRaw) - RequireEqualJSON(t, resRaw, resCache) - require.Equal(t, 2, countRequests(backend, "debug_getRawReceipts")) - }) - - t.Run("debug_getRawReceipts with more than 0 receipts should be cached", func(t *testing.T) { - backend.Reset() - hdlr.SetRoute("debug_getRawReceipts", "999", []string{"a"}) - resRaw, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb"}) - require.NoError(t, err) - resCache, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb"}) - require.NoError(t, err) - RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":[\"a\"]}"), resRaw) - RequireEqualJSON(t, resRaw, resCache) - require.Equal(t, 1, countRequests(backend, "debug_getRawReceipts")) - }) -} - -func TestBatchCaching(t *testing.T) { - redis, err := miniredis.Run() - require.NoError(t, err) - defer redis.Close() - - hdlr := NewBatchRPCResponseRouter() - hdlr.SetRoute("eth_chainId", "1", "0x420") - hdlr.SetRoute("net_version", "1", "0x1234") - hdlr.SetRoute("eth_call", "1", "dummy_call") - hdlr.SetRoute("eth_getBlockByHash", "1", "eth_getBlockByHash") - - backend := NewMockBackend(hdlr) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - require.NoError(t, os.Setenv("REDIS_URL", fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()))) - - config := ReadConfig("caching") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - // allow time for the block number fetcher to fire - time.Sleep(1500 * time.Millisecond) - - goodChainIdResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"0x420\", \"id\": 1}" - goodNetVersionResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 1}" - goodEthCallResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"dummy_call\", \"id\": 1}" - goodEthGetBlockByHash := "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByHash\", \"id\": 1}" - - res, _, err := client.SendBatchRPC( - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("1", "net_version", nil), - NewRPCReq("1", "eth_getBlockByHash", []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), - ) - require.NoError(t, err) - RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodNetVersionResponse, goodEthGetBlockByHash)), res) - require.Equal(t, 1, countRequests(backend, "eth_chainId")) - require.Equal(t, 1, countRequests(backend, "net_version")) - require.Equal(t, 1, countRequests(backend, "eth_getBlockByHash")) - - backend.Reset() - res, _, err = client.SendBatchRPC( - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("1", "eth_call", []interface{}{`{"to":"0x1234"}`, "pending"}), - NewRPCReq("1", "net_version", nil), - NewRPCReq("1", "eth_getBlockByHash", []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), - ) - require.NoError(t, err) - RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodEthCallResponse, goodNetVersionResponse, goodEthGetBlockByHash)), res) - require.Equal(t, 0, countRequests(backend, "eth_chainId")) - require.Equal(t, 0, countRequests(backend, "net_version")) - require.Equal(t, 0, countRequests(backend, "eth_getBlockByHash")) - require.Equal(t, 1, countRequests(backend, "eth_call")) -} - -func countRequests(backend *MockBackend, name string) int { - var count int - for _, req := range backend.Requests() { - if bytes.Contains(req.Body, []byte(name)) { - count++ - } - } - return count -}
diff --git OP/proxyd/integration_tests/consensus_test.go CELO/proxyd/integration_tests/consensus_test.go deleted file mode 100644 index 654b7a58c1778e39b036444f60412ec37531c87b..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/consensus_test.go +++ /dev/null @@ -1,1005 +0,0 @@ -package integration_tests - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "os" - "path" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/ethereum-optimism/optimism/proxyd" - ms "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" - "github.com/stretchr/testify/require" -) - -type nodeContext struct { - backend *proxyd.Backend // this is the actual backend impl in proxyd - mockBackend *MockBackend // this is the fake backend that we can use to mock responses - handler *ms.MockedHandler // this is where we control the state of mocked responses -} - -func setup(t *testing.T) (map[string]nodeContext, *proxyd.BackendGroup, *ProxydHTTPClient, func()) { - // setup mock servers - node1 := NewMockBackend(nil) - node2 := NewMockBackend(nil) - - dir, err := os.Getwd() - require.NoError(t, err) - - responses := path.Join(dir, "testdata/consensus_responses.yml") - - h1 := ms.MockedHandler{ - Overrides: []*ms.MethodTemplate{}, - Autoload: true, - AutoloadFile: responses, - } - h2 := ms.MockedHandler{ - Overrides: []*ms.MethodTemplate{}, - Autoload: true, - AutoloadFile: responses, - } - - require.NoError(t, os.Setenv("NODE1_URL", node1.URL())) - require.NoError(t, os.Setenv("NODE2_URL", node2.URL())) - - node1.SetHandler(http.HandlerFunc(h1.Handler)) - node2.SetHandler(http.HandlerFunc(h2.Handler)) - - // setup proxyd - config := ReadConfig("consensus") - svr, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - - // expose the proxyd client - client := NewProxydClient("http://127.0.0.1:8545") - - // expose the backend group - bg := svr.BackendGroups["node"] - require.NotNil(t, bg) - require.NotNil(t, bg.Consensus) - require.Equal(t, 2, len(bg.Backends)) // should match config - - // convenient mapping to access the nodes by name - nodes := map[string]nodeContext{ - "node1": { - mockBackend: node1, - backend: bg.Backends[0], - handler: &h1, - }, - "node2": { - mockBackend: node2, - backend: bg.Backends[1], - handler: &h2, - }, - } - - return nodes, bg, client, shutdown -} - -func TestConsensus(t *testing.T) { - nodes, bg, client, shutdown := setup(t) - defer nodes["node1"].mockBackend.Close() - defer nodes["node2"].mockBackend.Close() - defer shutdown() - - ctx := context.Background() - - // poll for updated consensus - update := func() { - for _, be := range bg.Backends { - bg.Consensus.UpdateBackend(ctx, be) - } - bg.Consensus.UpdateBackendGroupConsensus(ctx) - } - - // convenient methods to manipulate state and mock responses - reset := func() { - for _, node := range nodes { - node.handler.ResetOverrides() - node.mockBackend.Reset() - } - bg.Consensus.ClearListeners() - bg.Consensus.Reset() - } - - override := func(node string, method string, block string, response string) { - if _, ok := nodes[node]; !ok { - t.Fatalf("node %s does not exist in the nodes map", node) - } - nodes[node].handler.AddOverride(&ms.MethodTemplate{ - Method: method, - Block: block, - Response: response, - }) - } - - overrideBlock := func(node string, blockRequest string, blockResponse string) { - override(node, - "eth_getBlockByNumber", - blockRequest, - buildResponse(map[string]string{ - "number": blockResponse, - "hash": "hash_" + blockResponse, - })) - } - - overrideBlockHash := func(node string, blockRequest string, number string, hash string) { - override(node, - "eth_getBlockByNumber", - blockRequest, - buildResponse(map[string]string{ - "number": number, - "hash": hash, - })) - } - - overridePeerCount := func(node string, count int) { - override(node, "net_peerCount", "", buildResponse(hexutil.Uint64(count).String())) - } - - overrideNotInSync := func(node string) { - override(node, "eth_syncing", "", buildResponse(map[string]string{ - "startingblock": "0x0", - "currentblock": "0x0", - "highestblock": "0x100", - })) - } - - // force ban node2 and make sure node1 is the only one in consensus - useOnlyNode1 := func() { - overridePeerCount("node2", 0) - update() - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.Equal(t, 1, len(consensusGroup)) - require.Contains(t, consensusGroup, nodes["node1"].backend) - nodes["node1"].mockBackend.Reset() - } - - t.Run("initial consensus", func(t *testing.T) { - reset() - - // unknown consensus at init - require.Equal(t, "0x0", bg.Consensus.GetLatestBlockNumber().String()) - - // first poll - update() - - // as a default we use: - // - latest at 0x101 [257] - // - safe at 0xe1 [225] - // - finalized at 0xc1 [193] - - // consensus at block 0x101 - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - }) - - t.Run("prevent using a backend with low peer count", func(t *testing.T) { - reset() - overridePeerCount("node1", 0) - update() - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - }) - - t.Run("prevent using a backend lagging behind", func(t *testing.T) { - reset() - // node2 is 8+1 blocks ahead of node1 (0x101 + 8+1 = 0x10a) - overrideBlock("node2", "latest", "0x10a") - update() - - // since we ignored node1, the consensus should be at 0x10a - require.Equal(t, "0x10a", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - }) - - t.Run("prevent using a backend lagging behind - one before limit", func(t *testing.T) { - reset() - // node2 is 8 blocks ahead of node1 (0x101 + 8 = 0x109) - overrideBlock("node2", "latest", "0x109") - update() - - // both nodes are in consensus with the lowest block - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) - }) - - t.Run("prevent using a backend not in sync", func(t *testing.T) { - reset() - // make node1 not in sync - overrideNotInSync("node1") - update() - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - }) - - t.Run("advance consensus", func(t *testing.T) { - reset() - - // as a default we use: - // - latest at 0x101 [257] - // - safe at 0xe1 [225] - // - finalized at 0xc1 [193] - - update() - - // all nodes start at block 0x101 - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - - // advance latest on node2 to 0x102 - overrideBlock("node2", "latest", "0x102") - - update() - - // consensus should stick to 0x101, since node1 is still lagging there - bg.Consensus.UpdateBackendGroupConsensus(ctx) - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - - // advance latest on node1 to 0x102 - overrideBlock("node1", "latest", "0x102") - - update() - - // all nodes now at 0x102 - require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) - }) - - t.Run("should use lowest safe and finalized", func(t *testing.T) { - reset() - overrideBlock("node2", "finalized", "0xc2") - overrideBlock("node2", "safe", "0xe2") - update() - - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - }) - - t.Run("advance safe and finalized", func(t *testing.T) { - reset() - overrideBlock("node1", "finalized", "0xc2") - overrideBlock("node1", "safe", "0xe2") - overrideBlock("node2", "finalized", "0xc2") - overrideBlock("node2", "safe", "0xe2") - update() - - require.Equal(t, "0xe2", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc2", bg.Consensus.GetFinalizedBlockNumber().String()) - }) - - t.Run("ban backend if error rate is too high", func(t *testing.T) { - reset() - useOnlyNode1() - - // replace node1 handler with one that always returns 500 - oldHandler := nodes["node1"].mockBackend.handler - defer func() { nodes["node1"].mockBackend.handler = oldHandler }() - - nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(503) - })) - - numberReqs := 10 - for numberReqs > 0 { - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) - require.NoError(t, err) - require.Equal(t, 503, statusCode) - numberReqs-- - } - - update() - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 0, len(consensusGroup)) - }) - - t.Run("ban backend if tags are messed - safe < finalized", func(t *testing.T) { - reset() - overrideBlock("node1", "finalized", "0xb1") - overrideBlock("node1", "safe", "0xa1") - update() - - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - }) - - t.Run("ban backend if tags are messed - latest < safe", func(t *testing.T) { - reset() - overrideBlock("node1", "safe", "0xb1") - overrideBlock("node1", "latest", "0xa1") - update() - - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - }) - - t.Run("ban backend if tags are messed - safe dropped", func(t *testing.T) { - reset() - update() - overrideBlock("node1", "safe", "0xb1") - update() - - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - }) - - t.Run("ban backend if tags are messed - finalized dropped", func(t *testing.T) { - reset() - update() - overrideBlock("node1", "finalized", "0xa1") - update() - - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - }) - - t.Run("recover after safe and finalized dropped", func(t *testing.T) { - reset() - useOnlyNode1() - overrideBlock("node1", "latest", "0xd1") - overrideBlock("node1", "safe", "0xb1") - overrideBlock("node1", "finalized", "0x91") - update() - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 0, len(consensusGroup)) - - // unban and see if it recovers - bg.Consensus.Unban(nodes["node1"].backend) - update() - - consensusGroup = bg.Consensus.GetConsensusGroup() - require.Contains(t, consensusGroup, nodes["node1"].backend) - require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - - require.Equal(t, "0xd1", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) - }) - - t.Run("latest dropped below safe, then recovered", func(t *testing.T) { - reset() - useOnlyNode1() - overrideBlock("node1", "latest", "0xd1") - update() - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 0, len(consensusGroup)) - - // unban and see if it recovers - bg.Consensus.Unban(nodes["node1"].backend) - overrideBlock("node1", "safe", "0xb1") - overrideBlock("node1", "finalized", "0x91") - update() - - consensusGroup = bg.Consensus.GetConsensusGroup() - require.Contains(t, consensusGroup, nodes["node1"].backend) - require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 1, len(consensusGroup)) - - require.Equal(t, "0xd1", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) - }) - - t.Run("latest dropped below safe, and stayed inconsistent", func(t *testing.T) { - reset() - useOnlyNode1() - overrideBlock("node1", "latest", "0xd1") - update() - - consensusGroup := bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 0, len(consensusGroup)) - - // unban and see if it recovers - it should not since the blocks stays the same - bg.Consensus.Unban(nodes["node1"].backend) - update() - - // should be banned again - consensusGroup = bg.Consensus.GetConsensusGroup() - require.NotContains(t, consensusGroup, nodes["node1"].backend) - require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.Equal(t, 0, len(consensusGroup)) - }) - - t.Run("broken consensus", func(t *testing.T) { - reset() - listenerCalled := false - bg.Consensus.AddListener(func() { - listenerCalled = true - }) - update() - - // all nodes start at block 0x101 - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - - // advance latest on both nodes to 0x102 - overrideBlock("node1", "latest", "0x102") - overrideBlock("node2", "latest", "0x102") - - update() - - // at 0x102 - require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) - - // make node2 diverge on hash - overrideBlockHash("node2", "0x102", "0x102", "wrong_hash") - - update() - - // should resolve to 0x101, since 0x102 is out of consensus at the moment - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - - // everybody serving traffic - consensusGroup := bg.Consensus.GetConsensusGroup() - require.Equal(t, 2, len(consensusGroup)) - require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) - - // onConsensusBroken listener was called - require.True(t, listenerCalled) - }) - - t.Run("broken consensus with depth 2", func(t *testing.T) { - reset() - listenerCalled := false - bg.Consensus.AddListener(func() { - listenerCalled = true - }) - update() - - // all nodes start at block 0x101 - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - - // advance latest on both nodes to 0x102 - overrideBlock("node1", "latest", "0x102") - overrideBlock("node2", "latest", "0x102") - - update() - - // at 0x102 - require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) - - // advance latest on both nodes to 0x3 - overrideBlock("node1", "latest", "0x103") - overrideBlock("node2", "latest", "0x103") - - update() - - // at 0x103 - require.Equal(t, "0x103", bg.Consensus.GetLatestBlockNumber().String()) - - // make node2 diverge on hash for blocks 0x102 and 0x103 - overrideBlockHash("node2", "0x102", "0x102", "wrong_hash_0x102") - overrideBlockHash("node2", "0x103", "0x103", "wrong_hash_0x103") - - update() - - // should resolve to 0x101 - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - - // everybody serving traffic - consensusGroup := bg.Consensus.GetConsensusGroup() - require.Equal(t, 2, len(consensusGroup)) - require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) - - // onConsensusBroken listener was called - require.True(t, listenerCalled) - }) - - t.Run("fork in advanced block", func(t *testing.T) { - reset() - listenerCalled := false - bg.Consensus.AddListener(func() { - listenerCalled = true - }) - update() - - // all nodes start at block 0x101 - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - - // make nodes 1 and 2 advance in forks, i.e. they have same block number with different hashes - overrideBlockHash("node1", "0x102", "0x102", "node1_0x102") - overrideBlockHash("node2", "0x102", "0x102", "node2_0x102") - overrideBlockHash("node1", "0x103", "0x103", "node1_0x103") - overrideBlockHash("node2", "0x103", "0x103", "node2_0x103") - overrideBlockHash("node1", "latest", "0x103", "node1_0x103") - overrideBlockHash("node2", "latest", "0x103", "node2_0x103") - - update() - - // should resolve to 0x101, the highest common ancestor - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - - // everybody serving traffic - consensusGroup := bg.Consensus.GetConsensusGroup() - require.Equal(t, 2, len(consensusGroup)) - require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) - require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) - - // onConsensusBroken listener should not be called - require.False(t, listenerCalled) - }) - - t.Run("load balancing should hit both backends", func(t *testing.T) { - reset() - update() - - require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) - - // reset request counts - nodes["node1"].mockBackend.Reset() - nodes["node2"].mockBackend.Reset() - - require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) - require.Equal(t, 0, len(nodes["node2"].mockBackend.Requests())) - - // there is a random component to this test, - // since our round-robin implementation shuffles the ordering - // to achieve uniform distribution - - // so we just make 100 requests per backend and expect the number of requests to be somewhat balanced - // i.e. each backend should be hit minimally by at least 50% of the requests - consensusGroup := bg.Consensus.GetConsensusGroup() - - numberReqs := len(consensusGroup) * 100 - for numberReqs > 0 { - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - numberReqs-- - } - - msg := fmt.Sprintf("n1 %d, n2 %d", - len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) - require.GreaterOrEqual(t, len(nodes["node1"].mockBackend.Requests()), 50, msg) - require.GreaterOrEqual(t, len(nodes["node2"].mockBackend.Requests()), 50, msg) - }) - - t.Run("load balancing should not hit if node is not healthy", func(t *testing.T) { - reset() - useOnlyNode1() - - // reset request counts - nodes["node1"].mockBackend.Reset() - nodes["node2"].mockBackend.Reset() - - require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) - require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) - - numberReqs := 10 - for numberReqs > 0 { - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - numberReqs-- - } - - msg := fmt.Sprintf("n1 %d, n2 %d", - len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) - require.Equal(t, len(nodes["node1"].mockBackend.Requests()), 10, msg) - require.Equal(t, len(nodes["node2"].mockBackend.Requests()), 0, msg) - }) - - t.Run("load balancing should not hit if node is degraded", func(t *testing.T) { - reset() - useOnlyNode1() - - // replace node1 handler with one that adds a 500ms delay - oldHandler := nodes["node1"].mockBackend.handler - defer func() { nodes["node1"].mockBackend.handler = oldHandler }() - - nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - time.Sleep(500 * time.Millisecond) - oldHandler.ServeHTTP(w, r) - })) - - update() - - // send 10 requests to make node1 degraded - numberReqs := 10 - for numberReqs > 0 { - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - numberReqs-- - } - - // bring back node2 - nodes["node2"].handler.ResetOverrides() - update() - - // reset request counts - nodes["node1"].mockBackend.Reset() - nodes["node2"].mockBackend.Reset() - - require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) - require.Equal(t, 0, len(nodes["node2"].mockBackend.Requests())) - - numberReqs = 10 - for numberReqs > 0 { - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - numberReqs-- - } - - msg := fmt.Sprintf("n1 %d, n2 %d", - len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) - require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests()), msg) - require.Equal(t, 10, len(nodes["node2"].mockBackend.Requests()), msg) - }) - - t.Run("rewrite response of eth_blockNumber", func(t *testing.T) { - reset() - update() - - totalRequests := len(nodes["node1"].mockBackend.Requests()) + len(nodes["node2"].mockBackend.Requests()) - require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) - - resRaw, statusCode, err := client.SendRPC("eth_blockNumber", nil) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var jsonMap map[string]interface{} - err = json.Unmarshal(resRaw, &jsonMap) - require.NoError(t, err) - require.Equal(t, "0x101", jsonMap["result"]) - - // no extra request hit the backends - require.Equal(t, totalRequests, - len(nodes["node1"].mockBackend.Requests())+len(nodes["node2"].mockBackend.Requests())) - }) - - t.Run("rewrite request of eth_getBlockByNumber for latest", func(t *testing.T) { - reset() - useOnlyNode1() - - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"latest"}) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var jsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) - require.NoError(t, err) - require.Equal(t, "0x101", jsonMap["params"].([]interface{})[0]) - }) - - t.Run("rewrite request of eth_getBlockByNumber for finalized", func(t *testing.T) { - reset() - useOnlyNode1() - - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"finalized"}) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var jsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) - require.NoError(t, err) - require.Equal(t, "0xc1", jsonMap["params"].([]interface{})[0]) - }) - - t.Run("rewrite request of eth_getBlockByNumber for safe", func(t *testing.T) { - reset() - useOnlyNode1() - - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"safe"}) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var jsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) - require.NoError(t, err) - require.Equal(t, "0xe1", jsonMap["params"].([]interface{})[0]) - }) - - t.Run("rewrite request of eth_getBlockByNumber - out of range", func(t *testing.T) { - reset() - useOnlyNode1() - - resRaw, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x300"}) - require.NoError(t, err) - require.Equal(t, 400, statusCode) - - var jsonMap map[string]interface{} - err = json.Unmarshal(resRaw, &jsonMap) - require.NoError(t, err) - require.Equal(t, -32019, int(jsonMap["error"].(map[string]interface{})["code"].(float64))) - require.Equal(t, "block is out of range", jsonMap["error"].(map[string]interface{})["message"]) - }) - - t.Run("batched rewrite", func(t *testing.T) { - reset() - useOnlyNode1() - - resRaw, statusCode, err := client.SendBatchRPC( - NewRPCReq("1", "eth_getBlockByNumber", []interface{}{"latest"}), - NewRPCReq("2", "eth_getBlockByNumber", []interface{}{"0x102"}), - NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0xe1"})) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var jsonMap []map[string]interface{} - err = json.Unmarshal(resRaw, &jsonMap) - require.NoError(t, err) - require.Equal(t, 3, len(jsonMap)) - - // rewrite latest to 0x101 - require.Equal(t, "0x101", jsonMap[0]["result"].(map[string]interface{})["number"]) - - // out of bounds for block 0x102 - require.Equal(t, -32019, int(jsonMap[1]["error"].(map[string]interface{})["code"].(float64))) - require.Equal(t, "block is out of range", jsonMap[1]["error"].(map[string]interface{})["message"]) - - // dont rewrite for 0xe1 - require.Equal(t, "0xe1", jsonMap[2]["result"].(map[string]interface{})["number"]) - }) - - t.Run("translate consensus_getReceipts to debug_getRawReceipts", func(t *testing.T) { - reset() - useOnlyNode1() - update() - - // reset request counts - nodes["node1"].mockBackend.Reset() - - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", - []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var jsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) - require.NoError(t, err) - require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) - require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", jsonMap["params"].([]interface{})[0]) - - var resJsonMap map[string]interface{} - err = json.Unmarshal(resRaw, &resJsonMap) - require.NoError(t, err) - - require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) - require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) - }) - - t.Run("translate consensus_getReceipts to debug_getRawReceipts with latest block tag", func(t *testing.T) { - reset() - useOnlyNode1() - update() - - // reset request counts - nodes["node1"].mockBackend.Reset() - - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", - []interface{}{"latest"}) - - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var jsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) - require.NoError(t, err) - require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) - require.Equal(t, "0x101", jsonMap["params"].([]interface{})[0]) - - var resJsonMap map[string]interface{} - err = json.Unmarshal(resRaw, &resJsonMap) - require.NoError(t, err) - - require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) - require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) - }) - - t.Run("translate consensus_getReceipts to debug_getRawReceipts with block number", func(t *testing.T) { - reset() - useOnlyNode1() - update() - - // reset request counts - nodes["node1"].mockBackend.Reset() - - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", - []interface{}{"0x55"}) - - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var jsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) - require.NoError(t, err) - require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) - require.Equal(t, "0x55", jsonMap["params"].([]interface{})[0]) - - var resJsonMap map[string]interface{} - err = json.Unmarshal(resRaw, &resJsonMap) - require.NoError(t, err) - - require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) - require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) - }) - - t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with block hash", func(t *testing.T) { - reset() - useOnlyNode1() - update() - - // reset request counts - nodes["node1"].mockBackend.Reset() - - nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) - defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) - - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", - []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}) - - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var reqJsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) - - require.NoError(t, err) - require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) - require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockHash"]) - - var resJsonMap map[string]interface{} - err = json.Unmarshal(resRaw, &resJsonMap) - require.NoError(t, err) - - require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) - require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) - }) - - t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with block number", func(t *testing.T) { - reset() - useOnlyNode1() - update() - - // reset request counts - nodes["node1"].mockBackend.Reset() - - nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) - defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) - - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", - []interface{}{"0x55"}) - - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var reqJsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) - - require.NoError(t, err) - require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) - require.Equal(t, "0x55", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockNumber"]) - - var resJsonMap map[string]interface{} - err = json.Unmarshal(resRaw, &resJsonMap) - require.NoError(t, err) - - require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) - require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) - }) - - t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with latest block tag", func(t *testing.T) { - reset() - useOnlyNode1() - update() - - // reset request counts - nodes["node1"].mockBackend.Reset() - - nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) - defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) - - resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", - []interface{}{"latest"}) - - require.NoError(t, err) - require.Equal(t, 200, statusCode) - - var reqJsonMap map[string]interface{} - err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) - - require.NoError(t, err) - require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) - require.Equal(t, "0x101", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockNumber"]) - - var resJsonMap map[string]interface{} - err = json.Unmarshal(resRaw, &resJsonMap) - require.NoError(t, err) - - require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) - require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) - }) - - t.Run("translate consensus_getReceipts to unsupported consensus_receipts_target", func(t *testing.T) { - reset() - useOnlyNode1() - - nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("unsupported_consensus_receipts_target")) - defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) - - _, statusCode, err := client.SendRPC("consensus_getReceipts", - []interface{}{"latest"}) - - require.NoError(t, err) - require.Equal(t, 400, statusCode) - }) - - t.Run("consensus_getReceipts should not be used in a batch", func(t *testing.T) { - reset() - useOnlyNode1() - - _, statusCode, err := client.SendBatchRPC( - NewRPCReq("1", "eth_getBlockByNumber", []interface{}{"latest"}), - NewRPCReq("2", "consensus_getReceipts", []interface{}{"0x55"}), - NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0xe1"})) - require.NoError(t, err) - require.Equal(t, 400, statusCode) - }) -} - -func buildResponse(result interface{}) string { - res, err := json.Marshal(proxyd.RPCRes{ - Result: result, - }) - if err != nil { - panic(err) - } - return string(res) -}
diff --git OP/proxyd/integration_tests/failover_test.go CELO/proxyd/integration_tests/failover_test.go deleted file mode 100644 index 501542a1effb2a9513e37ff424e8d7b74da642d0..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/failover_test.go +++ /dev/null @@ -1,288 +0,0 @@ -package integration_tests - -import ( - "fmt" - "net/http" - "os" - "sync/atomic" - "testing" - "time" - - "github.com/alicebob/miniredis" - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" -) - -const ( - goodResponse = `{"jsonrpc": "2.0", "result": "hello", "id": 999}` - noBackendsResponse = `{"error":{"code":-32011,"message":"no backends available for method"},"id":999,"jsonrpc":"2.0"}` - unexpectedResponse = `{"error":{"code":-32011,"message":"some error"},"id":999,"jsonrpc":"2.0"}` -) - -func TestFailover(t *testing.T) { - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer goodBackend.Close() - badBackend := NewMockBackend(nil) - defer badBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) - - config := ReadConfig("failover") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - tests := []struct { - name string - handler http.Handler - }{ - { - "backend responds 200 with non-JSON response", - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - _, _ = w.Write([]byte("this data is not JSON!")) - }), - }, - { - "backend responds with no body", - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - }), - }, - } - codes := []int{ - 300, - 301, - 302, - 401, - 403, - 429, - 500, - 503, - } - for _, code := range codes { - tests = append(tests, struct { - name string - handler http.Handler - }{ - fmt.Sprintf("backend %d", code), - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(code) - }), - }) - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - badBackend.SetHandler(tt.handler) - res, statusCode, err := client.SendRPC("eth_chainId", nil) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 1, len(badBackend.Requests())) - require.Equal(t, 1, len(goodBackend.Requests())) - badBackend.Reset() - goodBackend.Reset() - }) - } - - t.Run("backend times out and falls back to another", func(t *testing.T) { - badBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - time.Sleep(2 * time.Second) - _, _ = w.Write([]byte("[{}]")) - })) - res, statusCode, err := client.SendRPC("eth_chainId", nil) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 1, len(badBackend.Requests())) - require.Equal(t, 1, len(goodBackend.Requests())) - goodBackend.Reset() - badBackend.Reset() - }) - - t.Run("works with a batch request", func(t *testing.T) { - goodBackend.SetHandler(BatchedResponseHandler(200, goodResponse, goodResponse)) - badBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(500) - })) - res, statusCode, err := client.SendBatchRPC( - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("2", "eth_chainId", nil), - ) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(asArray(goodResponse, goodResponse)), res) - require.Equal(t, 1, len(badBackend.Requests())) - require.Equal(t, 1, len(goodBackend.Requests())) - goodBackend.Reset() - badBackend.Reset() - }) -} - -func TestRetries(t *testing.T) { - backend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - config := ReadConfig("retries") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - attempts := int32(0) - backend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - incremented := atomic.AddInt32(&attempts, 1) - if incremented != 2 { - w.WriteHeader(500) - return - } - BatchedResponseHandler(200, goodResponse)(w, r) - })) - - // test case where request eventually succeeds - res, statusCode, err := client.SendRPC("eth_chainId", nil) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 2, len(backend.Requests())) - - // test case where it does not - backend.Reset() - attempts = -10 - res, statusCode, err = client.SendRPC("eth_chainId", nil) - require.NoError(t, err) - require.Equal(t, 503, statusCode) - RequireEqualJSON(t, []byte(noBackendsResponse), res) - require.Equal(t, 4, len(backend.Requests())) -} - -func TestOutOfServiceInterval(t *testing.T) { - okHandler := BatchedResponseHandler(200, goodResponse) - goodBackend := NewMockBackend(okHandler) - defer goodBackend.Close() - badBackend := NewMockBackend(nil) - defer badBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) - - config := ReadConfig("out_of_service_interval") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - badBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(503) - })) - - res, statusCode, err := client.SendRPC("eth_chainId", nil) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 2, len(badBackend.Requests())) - require.Equal(t, 1, len(goodBackend.Requests())) - - res, statusCode, err = client.SendRPC("eth_chainId", nil) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 4, len(badBackend.Requests())) - require.Equal(t, 2, len(goodBackend.Requests())) - - _, statusCode, err = client.SendBatchRPC( - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("1", "eth_chainId", nil), - ) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - require.Equal(t, 8, len(badBackend.Requests())) - require.Equal(t, 4, len(goodBackend.Requests())) - - time.Sleep(time.Second) - badBackend.SetHandler(okHandler) - - res, statusCode, err = client.SendRPC("eth_chainId", nil) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 9, len(badBackend.Requests())) - require.Equal(t, 4, len(goodBackend.Requests())) -} - -func TestBatchWithPartialFailover(t *testing.T) { - config := ReadConfig("failover") - config.Server.MaxUpstreamBatchSize = 2 - - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse, goodResponse)) - defer goodBackend.Close() - badBackend := NewMockBackend(SingleResponseHandler(200, "this data is not JSON!")) - defer badBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) - - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - res, statusCode, err := client.SendBatchRPC( - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("2", "eth_chainId", nil), - NewRPCReq("3", "eth_chainId", nil), - NewRPCReq("4", "eth_chainId", nil), - ) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(asArray(goodResponse, goodResponse, goodResponse, goodResponse)), res) - require.Equal(t, 2, len(badBackend.Requests())) - require.Equal(t, 2, len(goodBackend.Requests())) -} - -func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { - InitLogger() - // Scenario: - // 1. Send batch to BAD_BACKEND (Infura) - // 2. Infura fails completely due to a partially errorneous batch request (one of N+1 request object is invalid) - // 3. Assert that the request batch is re-routed to the failover provider - // 4. Assert that BAD_BACKEND is NOT labeled offline - // 5. Assert that BAD_BACKEND is NOT retried - - redis, err := miniredis.Run() - require.NoError(t, err) - defer redis.Close() - - config := ReadConfig("failover") - config.Server.MaxUpstreamBatchSize = 2 - config.BackendOptions.MaxRetries = 2 - // Setup redis to detect offline backends - config.Redis.URL = fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()) - require.NoError(t, err) - - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse, goodResponse)) - defer goodBackend.Close() - badBackend := NewMockBackend(SingleResponseHandler(200, unexpectedResponse)) - defer badBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) - - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - res, statusCode, err := client.SendBatchRPC( - NewRPCReq("1", "eth_chainId", nil), - NewRPCReq("2", "eth_chainId", nil), - ) - require.NoError(t, err) - require.Equal(t, 200, statusCode) - RequireEqualJSON(t, []byte(asArray(goodResponse, goodResponse)), res) - require.Equal(t, 1, len(badBackend.Requests())) - require.Equal(t, 1, len(goodBackend.Requests())) -}
diff --git OP/proxyd/integration_tests/fallback_test.go CELO/proxyd/integration_tests/fallback_test.go deleted file mode 100644 index c5b3e48235177ac4d67092bd7a29c089024b2d0e..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/fallback_test.go +++ /dev/null @@ -1,374 +0,0 @@ -package integration_tests - -import ( - "context" - "fmt" - "net/http" - "os" - "path" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/ethereum-optimism/optimism/proxyd" - ms "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" - "github.com/stretchr/testify/require" -) - -func setup_failover(t *testing.T) (map[string]nodeContext, *proxyd.BackendGroup, *ProxydHTTPClient, func(), []time.Time, []time.Time) { - // setup mock servers - node1 := NewMockBackend(nil) - node2 := NewMockBackend(nil) - - dir, err := os.Getwd() - require.NoError(t, err) - - responses := path.Join(dir, "testdata/consensus_responses.yml") - - h1 := ms.MockedHandler{ - Overrides: []*ms.MethodTemplate{}, - Autoload: true, - AutoloadFile: responses, - } - h2 := ms.MockedHandler{ - Overrides: []*ms.MethodTemplate{}, - Autoload: true, - AutoloadFile: responses, - } - - require.NoError(t, os.Setenv("NODE1_URL", node1.URL())) - require.NoError(t, os.Setenv("NODE2_URL", node2.URL())) - - node1.SetHandler(http.HandlerFunc(h1.Handler)) - node2.SetHandler(http.HandlerFunc(h2.Handler)) - - // setup proxyd - config := ReadConfig("fallback") - svr, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - - // expose the proxyd client - client := NewProxydClient("http://127.0.0.1:8545") - - // expose the backend group - bg := svr.BackendGroups["node"] - require.NotNil(t, bg) - require.NotNil(t, bg.Consensus) - require.Equal(t, 2, len(bg.Backends)) // should match config - - // convenient mapping to access the nodes by name - nodes := map[string]nodeContext{ - "normal": { - mockBackend: node1, - backend: bg.Backends[0], - handler: &h1, - }, - "fallback": { - mockBackend: node2, - backend: bg.Backends[1], - handler: &h2, - }, - } - normalTimestamps := []time.Time{} - fallbackTimestamps := []time.Time{} - - return nodes, bg, client, shutdown, normalTimestamps, fallbackTimestamps -} - -func TestFallback(t *testing.T) { - nodes, bg, client, shutdown, normalTimestamps, fallbackTimestamps := setup_failover(t) - defer nodes["normal"].mockBackend.Close() - defer nodes["fallback"].mockBackend.Close() - defer shutdown() - - ctx := context.Background() - - // Use Update to Advance the Candidate iteration - update := func() { - for _, be := range bg.Primaries() { - bg.Consensus.UpdateBackend(ctx, be) - } - - for _, be := range bg.Fallbacks() { - healthyCandidates := bg.Consensus.FilterCandidates(bg.Primaries()) - if len(healthyCandidates) == 0 { - bg.Consensus.UpdateBackend(ctx, be) - } - } - - bg.Consensus.UpdateBackendGroupConsensus(ctx) - } - - override := func(node string, method string, block string, response string) { - if _, ok := nodes[node]; !ok { - t.Fatalf("node %s does not exist in the nodes map", node) - } - nodes[node].handler.AddOverride(&ms.MethodTemplate{ - Method: method, - Block: block, - Response: response, - }) - } - - overrideBlock := func(node string, blockRequest string, blockResponse string) { - override(node, - "eth_getBlockByNumber", - blockRequest, - buildResponse(map[string]string{ - "number": blockResponse, - "hash": "hash_" + blockResponse, - })) - } - - overrideBlockHash := func(node string, blockRequest string, number string, hash string) { - override(node, - "eth_getBlockByNumber", - blockRequest, - buildResponse(map[string]string{ - "number": number, - "hash": hash, - })) - } - - overridePeerCount := func(node string, count int) { - override(node, "net_peerCount", "", buildResponse(hexutil.Uint64(count).String())) - } - - overrideNotInSync := func(node string) { - override(node, "eth_syncing", "", buildResponse(map[string]string{ - "startingblock": "0x0", - "currentblock": "0x0", - "highestblock": "0x100", - })) - } - - containsNode := func(backends []*proxyd.Backend, name string) bool { - for _, be := range backends { - // Note: Currently checks for name but would like to expose fallback better - if be.Name == name { - return true - } - } - return false - } - - // TODO: Improvement instead of simple array, - // ensure normal and backend are returned in strict order - recordLastUpdates := func(backends []*proxyd.Backend) []time.Time { - lastUpdated := []time.Time{} - for _, be := range backends { - lastUpdated = append(lastUpdated, bg.Consensus.GetLastUpdate(be)) - } - return lastUpdated - } - - // convenient methods to manipulate state and mock responses - reset := func() { - for _, node := range nodes { - node.handler.ResetOverrides() - node.mockBackend.Reset() - } - bg.Consensus.ClearListeners() - bg.Consensus.Reset() - - normalTimestamps = []time.Time{} - fallbackTimestamps = []time.Time{} - } - - /* - triggerFirstNormalFailure: will trigger consensus group into fallback mode - old consensus group should be returned one time, and fallback group should be enabled - Fallback will be returned subsequent update - */ - triggerFirstNormalFailure := func() { - overridePeerCount("normal", 0) - update() - require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) - require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - nodes["fallback"].mockBackend.Reset() - } - - t.Run("Test fallback Mode will not be exited, unless state changes", func(t *testing.T) { - reset() - triggerFirstNormalFailure() - for i := 0; i < 10; i++ { - update() - require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) - require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - } - }) - - t.Run("Test Healthy mode will not be exited unless state changes", func(t *testing.T) { - reset() - for i := 0; i < 10; i++ { - update() - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) - require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) - - _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) - - require.Equal(t, 200, statusCode) - require.Nil(t, err, "error not nil") - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - } - // TODO: Remove these, just here so compiler doesn't complain - overrideNotInSync("normal") - overrideBlock("normal", "safe", "0xb1") - overrideBlockHash("fallback", "0x102", "0x102", "wrong_hash") - }) - - t.Run("trigger normal failure, subsequent update return failover in consensus group, and fallback mode enabled", func(t *testing.T) { - reset() - triggerFirstNormalFailure() - update() - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) - require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) - }) - - t.Run("trigger healthy -> fallback, update -> healthy", func(t *testing.T) { - reset() - update() - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) - require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) - - triggerFirstNormalFailure() - update() - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) - require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) - - overridePeerCount("normal", 5) - update() - require.Equal(t, 1, len(bg.Consensus.GetConsensusGroup())) - require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) - require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) - }) - - t.Run("Ensure fallback is not updated when in normal mode", func(t *testing.T) { - reset() - for i := 0; i < 10; i++ { - update() - ts := recordLastUpdates(bg.Backends) - normalTimestamps = append(normalTimestamps, ts[0]) - fallbackTimestamps = append(fallbackTimestamps, ts[1]) - - require.False(t, normalTimestamps[i].IsZero()) - require.True(t, fallbackTimestamps[i].IsZero()) - - require.True(t, containsNode(bg.Consensus.GetConsensusGroup(), "normal")) - require.False(t, containsNode(bg.Consensus.GetConsensusGroup(), "fallback")) - - // consensus at block 0x101 - require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) - require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) - require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) - } - }) - - /* - Set Normal backend to Fail -> both backends should be updated - */ - t.Run("Ensure both nodes are quieried in fallback mode", func(t *testing.T) { - reset() - triggerFirstNormalFailure() - for i := 0; i < 10; i++ { - update() - ts := recordLastUpdates(bg.Backends) - normalTimestamps = append(normalTimestamps, ts[0]) - fallbackTimestamps = append(fallbackTimestamps, ts[1]) - - // Both Nodes should be updated again - require.False(t, normalTimestamps[i].IsZero()) - require.False(t, fallbackTimestamps[i].IsZero(), - fmt.Sprintf("Error: Fallback timestamp: %v was not queried on iteratio %d", fallbackTimestamps[i], i), - ) - if i > 0 { - require.Greater(t, normalTimestamps[i], normalTimestamps[i-1]) - require.Greater(t, fallbackTimestamps[i], fallbackTimestamps[i-1]) - } - } - }) - - t.Run("Ensure both nodes are quieried in fallback mode", func(t *testing.T) { - reset() - triggerFirstNormalFailure() - for i := 0; i < 10; i++ { - update() - ts := recordLastUpdates(bg.Backends) - normalTimestamps = append(normalTimestamps, ts[0]) - fallbackTimestamps = append(fallbackTimestamps, ts[1]) - - // Both Nodes should be updated again - require.False(t, normalTimestamps[i].IsZero()) - require.False(t, fallbackTimestamps[i].IsZero(), - fmt.Sprintf("Error: Fallback timestamp: %v was not queried on iteratio %d", fallbackTimestamps[i], i), - ) - if i > 0 { - require.Greater(t, normalTimestamps[i], normalTimestamps[i-1]) - require.Greater(t, fallbackTimestamps[i], fallbackTimestamps[i-1]) - } - } - }) - t.Run("Healthy -> Fallback -> Healthy with timestamps", func(t *testing.T) { - reset() - for i := 0; i < 10; i++ { - update() - ts := recordLastUpdates(bg.Backends) - normalTimestamps = append(normalTimestamps, ts[0]) - fallbackTimestamps = append(fallbackTimestamps, ts[1]) - - // Normal is queried, fallback is not - require.False(t, normalTimestamps[i].IsZero()) - require.True(t, fallbackTimestamps[i].IsZero(), - fmt.Sprintf("Error: Fallback timestamp: %v was not queried on iteratio %d", fallbackTimestamps[i], i), - ) - if i > 0 { - require.Greater(t, normalTimestamps[i], normalTimestamps[i-1]) - // Fallbacks should be zeros - require.Equal(t, fallbackTimestamps[i], fallbackTimestamps[i-1]) - } - } - - offset := 10 - triggerFirstNormalFailure() - for i := 0; i < 10; i++ { - update() - ts := recordLastUpdates(bg.Backends) - normalTimestamps = append(normalTimestamps, ts[0]) - fallbackTimestamps = append(fallbackTimestamps, ts[1]) - - // Both Nodes should be updated again - require.False(t, normalTimestamps[i+offset].IsZero()) - require.False(t, fallbackTimestamps[i+offset].IsZero()) - - require.Greater(t, normalTimestamps[i+offset], normalTimestamps[i+offset-1]) - require.Greater(t, fallbackTimestamps[i+offset], fallbackTimestamps[i+offset-1]) - } - - overridePeerCount("normal", 5) - offset = 20 - for i := 0; i < 10; i++ { - update() - ts := recordLastUpdates(bg.Backends) - normalTimestamps = append(normalTimestamps, ts[0]) - fallbackTimestamps = append(fallbackTimestamps, ts[1]) - - // Normal Node will be updated - require.False(t, normalTimestamps[i+offset].IsZero()) - require.Greater(t, normalTimestamps[i+offset], normalTimestamps[i+offset-1]) - - // fallback should not be updating - if offset+i > 21 { - require.Equal(t, fallbackTimestamps[i+offset], fallbackTimestamps[i+offset-1]) - } - } - }) -}
diff --git OP/proxyd/integration_tests/max_rpc_conns_test.go CELO/proxyd/integration_tests/max_rpc_conns_test.go deleted file mode 100644 index 5e2336443bfa480b97498d5da36a246da9512414..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/max_rpc_conns_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package integration_tests - -import ( - "net/http" - "net/http/httptest" - "os" - "sync" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" -) - -func TestMaxConcurrentRPCs(t *testing.T) { - var ( - mu sync.Mutex - concurrentRPCs int - maxConcurrentRPCs int - ) - handler := func(w http.ResponseWriter, r *http.Request) { - mu.Lock() - concurrentRPCs++ - if maxConcurrentRPCs < concurrentRPCs { - maxConcurrentRPCs = concurrentRPCs - } - mu.Unlock() - - time.Sleep(time.Second * 2) - BatchedResponseHandler(200, goodResponse)(w, r) - - mu.Lock() - concurrentRPCs-- - mu.Unlock() - } - // We don't use the MockBackend because it serializes requests to the handler - slowBackend := httptest.NewServer(http.HandlerFunc(handler)) - defer slowBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", slowBackend.URL)) - - config := ReadConfig("max_rpc_conns") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - type resWithCodeErr struct { - res []byte - code int - err error - } - resCh := make(chan *resWithCodeErr) - for i := 0; i < 3; i++ { - go func() { - res, code, err := client.SendRPC("eth_chainId", nil) - resCh <- &resWithCodeErr{ - res: res, - code: code, - err: err, - } - }() - } - res1 := <-resCh - res2 := <-resCh - res3 := <-resCh - - require.NoError(t, res1.err) - require.NoError(t, res2.err) - require.NoError(t, res3.err) - require.Equal(t, 200, res1.code) - require.Equal(t, 200, res2.code) - require.Equal(t, 200, res3.code) - RequireEqualJSON(t, []byte(goodResponse), res1.res) - RequireEqualJSON(t, []byte(goodResponse), res2.res) - RequireEqualJSON(t, []byte(goodResponse), res3.res) - - require.EqualValues(t, 2, maxConcurrentRPCs) -}
diff --git OP/proxyd/integration_tests/mock_backend_test.go CELO/proxyd/integration_tests/mock_backend_test.go deleted file mode 100644 index bf45d03f1cbb23f1e5e531c9862e422fc70cf78c..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/mock_backend_test.go +++ /dev/null @@ -1,327 +0,0 @@ -package integration_tests - -import ( - "bytes" - "context" - "encoding/json" - "io" - "net/http" - "net/http/httptest" - "strings" - "sync" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/gorilla/websocket" -) - -type RecordedRequest struct { - Method string - Headers http.Header - Body []byte -} - -type MockBackend struct { - handler http.Handler - server *httptest.Server - mtx sync.RWMutex - requests []*RecordedRequest -} - -func SingleResponseHandler(code int, response string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(code) - _, _ = w.Write([]byte(response)) - } -} - -func BatchedResponseHandler(code int, responses ...string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if len(responses) == 1 { - SingleResponseHandler(code, responses[0])(w, r) - return - } - - var body string - body += "[" - for i, response := range responses { - body += response - if i+1 < len(responses) { - body += "," - } - } - body += "]" - SingleResponseHandler(code, body)(w, r) - } -} - -type responseMapping struct { - result interface{} - calls int -} -type BatchRPCResponseRouter struct { - m map[string]map[string]*responseMapping - fallback map[string]interface{} - mtx sync.Mutex -} - -func NewBatchRPCResponseRouter() *BatchRPCResponseRouter { - return &BatchRPCResponseRouter{ - m: make(map[string]map[string]*responseMapping), - fallback: make(map[string]interface{}), - } -} - -func (h *BatchRPCResponseRouter) SetRoute(method string, id string, result interface{}) { - h.mtx.Lock() - defer h.mtx.Unlock() - - switch result.(type) { - case string: - case []string: - case nil: - break - default: - panic("invalid result type") - } - - m := h.m[method] - if m == nil { - m = make(map[string]*responseMapping) - } - m[id] = &responseMapping{result: result} - h.m[method] = m -} - -func (h *BatchRPCResponseRouter) SetFallbackRoute(method string, result interface{}) { - h.mtx.Lock() - defer h.mtx.Unlock() - - switch result.(type) { - case string: - case nil: - break - default: - panic("invalid result type") - } - - h.fallback[method] = result -} - -func (h *BatchRPCResponseRouter) GetNumCalls(method string, id string) int { - h.mtx.Lock() - defer h.mtx.Unlock() - - if m := h.m[method]; m != nil { - if rm := m[id]; rm != nil { - return rm.calls - } - } - return 0 -} - -func (h *BatchRPCResponseRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.mtx.Lock() - defer h.mtx.Unlock() - - body, err := io.ReadAll(r.Body) - if err != nil { - panic(err) - } - - if proxyd.IsBatch(body) { - batch, err := proxyd.ParseBatchRPCReq(body) - if err != nil { - panic(err) - } - out := make([]*proxyd.RPCRes, len(batch)) - for i := range batch { - req, err := proxyd.ParseRPCReq(batch[i]) - if err != nil { - panic(err) - } - - var result interface{} - var resultHasValue bool - - if mappings, exists := h.m[req.Method]; exists { - if rm := mappings[string(req.ID)]; rm != nil { - result = rm.result - resultHasValue = true - rm.calls++ - } - } - if !resultHasValue { - result, resultHasValue = h.fallback[req.Method] - } - if !resultHasValue { - w.WriteHeader(400) - return - } - - out[i] = &proxyd.RPCRes{ - JSONRPC: proxyd.JSONRPCVersion, - Result: result, - ID: req.ID, - } - } - if err := json.NewEncoder(w).Encode(out); err != nil { - panic(err) - } - return - } - - req, err := proxyd.ParseRPCReq(body) - if err != nil { - panic(err) - } - - var result interface{} - var resultHasValue bool - - if mappings, exists := h.m[req.Method]; exists { - if rm := mappings[string(req.ID)]; rm != nil { - result = rm.result - resultHasValue = true - rm.calls++ - } - } - if !resultHasValue { - result, resultHasValue = h.fallback[req.Method] - } - if !resultHasValue { - w.WriteHeader(400) - return - } - - out := &proxyd.RPCRes{ - JSONRPC: proxyd.JSONRPCVersion, - Result: result, - ID: req.ID, - } - enc := json.NewEncoder(w) - if err := enc.Encode(out); err != nil { - panic(err) - } -} - -func NewMockBackend(handler http.Handler) *MockBackend { - mb := &MockBackend{ - handler: handler, - } - mb.server = httptest.NewServer(http.HandlerFunc(mb.wrappedHandler)) - return mb -} - -func (m *MockBackend) URL() string { - return m.server.URL -} - -func (m *MockBackend) Close() { - m.server.Close() -} - -func (m *MockBackend) SetHandler(handler http.Handler) { - m.mtx.Lock() - m.handler = handler - m.mtx.Unlock() -} - -func (m *MockBackend) Reset() { - m.mtx.Lock() - m.requests = nil - m.mtx.Unlock() -} - -func (m *MockBackend) Requests() []*RecordedRequest { - m.mtx.RLock() - defer m.mtx.RUnlock() - out := make([]*RecordedRequest, len(m.requests)) - copy(out, m.requests) - return out -} - -func (m *MockBackend) wrappedHandler(w http.ResponseWriter, r *http.Request) { - m.mtx.Lock() - body, err := io.ReadAll(r.Body) - if err != nil { - panic(err) - } - clone := r.Clone(context.Background()) - clone.Body = io.NopCloser(bytes.NewReader(body)) - m.requests = append(m.requests, &RecordedRequest{ - Method: r.Method, - Headers: r.Header.Clone(), - Body: body, - }) - m.handler.ServeHTTP(w, clone) - m.mtx.Unlock() -} - -type MockWSBackend struct { - connCB MockWSBackendOnConnect - msgCB MockWSBackendOnMessage - closeCB MockWSBackendOnClose - server *httptest.Server - upgrader websocket.Upgrader - conns []*websocket.Conn - connsMu sync.Mutex -} - -type MockWSBackendOnConnect func(conn *websocket.Conn) -type MockWSBackendOnMessage func(conn *websocket.Conn, msgType int, data []byte) -type MockWSBackendOnClose func(conn *websocket.Conn, err error) - -func NewMockWSBackend( - connCB MockWSBackendOnConnect, - msgCB MockWSBackendOnMessage, - closeCB MockWSBackendOnClose, -) *MockWSBackend { - mb := &MockWSBackend{ - connCB: connCB, - msgCB: msgCB, - closeCB: closeCB, - } - mb.server = httptest.NewServer(mb) - return mb -} - -func (m *MockWSBackend) ServeHTTP(w http.ResponseWriter, r *http.Request) { - conn, err := m.upgrader.Upgrade(w, r, nil) - if err != nil { - panic(err) - } - if m.connCB != nil { - m.connCB(conn) - } - go func() { - for { - mType, msg, err := conn.ReadMessage() - if err != nil { - if m.closeCB != nil { - m.closeCB(conn, err) - } - return - } - if m.msgCB != nil { - m.msgCB(conn, mType, msg) - } - } - }() - m.connsMu.Lock() - m.conns = append(m.conns, conn) - m.connsMu.Unlock() -} - -func (m *MockWSBackend) URL() string { - return strings.Replace(m.server.URL, "http://", "ws://", 1) -} - -func (m *MockWSBackend) Close() { - m.server.Close() - - m.connsMu.Lock() - for _, conn := range m.conns { - conn.Close() - } - m.connsMu.Unlock() -}
diff --git OP/proxyd/integration_tests/rate_limit_test.go CELO/proxyd/integration_tests/rate_limit_test.go deleted file mode 100644 index 4e17f625c1189147a3d1541800f34c252883d0e9..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/rate_limit_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package integration_tests - -import ( - "encoding/json" - "net/http" - "os" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" -) - -type resWithCode struct { - code int - res []byte -} - -const frontendOverLimitResponse = `{"error":{"code":-32016,"message":"over rate limit with special message"},"id":null,"jsonrpc":"2.0"}` -const frontendOverLimitResponseWithID = `{"error":{"code":-32016,"message":"over rate limit with special message"},"id":999,"jsonrpc":"2.0"}` - -var ethChainID = "eth_chainId" - -func TestFrontendMaxRPSLimit(t *testing.T) { - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("frontend_rate_limit") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - t.Run("non-exempt over limit", func(t *testing.T) { - client := NewProxydClient("http://127.0.0.1:8545") - limitedRes, codes := spamReqs(t, client, ethChainID, 429, 3) - require.Equal(t, 1, codes[429]) - require.Equal(t, 2, codes[200]) - RequireEqualJSON(t, []byte(frontendOverLimitResponse), limitedRes) - }) - - t.Run("exempt user agent over limit", func(t *testing.T) { - h := make(http.Header) - h.Set("User-Agent", "exempt_agent") - client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - _, codes := spamReqs(t, client, ethChainID, 429, 3) - require.Equal(t, 3, codes[200]) - }) - - t.Run("exempt origin over limit", func(t *testing.T) { - h := make(http.Header) - h.Set("Origin", "exempt_origin") - client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - _, codes := spamReqs(t, client, ethChainID, 429, 3) - require.Equal(t, 3, codes[200]) - }) - - t.Run("multiple xff", func(t *testing.T) { - h1 := make(http.Header) - h1.Set("X-Forwarded-For", "0.0.0.0") - h2 := make(http.Header) - h2.Set("X-Forwarded-For", "1.1.1.1") - client1 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h1) - client2 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h2) - _, codes := spamReqs(t, client1, ethChainID, 429, 3) - require.Equal(t, 1, codes[429]) - require.Equal(t, 2, codes[200]) - _, code, err := client2.SendRPC(ethChainID, nil) - require.Equal(t, 200, code) - require.NoError(t, err) - time.Sleep(time.Second) - _, code, err = client2.SendRPC(ethChainID, nil) - require.Equal(t, 200, code) - require.NoError(t, err) - }) - - time.Sleep(time.Second) - - t.Run("RPC override", func(t *testing.T) { - client := NewProxydClient("http://127.0.0.1:8545") - limitedRes, codes := spamReqs(t, client, "eth_foobar", 429, 2) - // use 2 and 1 here since the limit for eth_foobar is 1 - require.Equal(t, 1, codes[429]) - require.Equal(t, 1, codes[200]) - RequireEqualJSON(t, []byte(frontendOverLimitResponseWithID), limitedRes) - }) - - time.Sleep(time.Second) - - t.Run("RPC override in batch", func(t *testing.T) { - client := NewProxydClient("http://127.0.0.1:8545") - req := NewRPCReq("123", "eth_foobar", nil) - out, code, err := client.SendBatchRPC(req, req, req) - require.NoError(t, err) - var res []proxyd.RPCRes - require.NoError(t, json.Unmarshal(out, &res)) - - expCode := proxyd.ErrOverRateLimit.Code - require.Equal(t, 200, code) - require.Equal(t, 3, len(res)) - require.Nil(t, res[0].Error) - require.Equal(t, expCode, res[1].Error.Code) - require.Equal(t, expCode, res[2].Error.Code) - }) - - time.Sleep(time.Second) - - t.Run("RPC override in batch exempt", func(t *testing.T) { - h := make(http.Header) - h.Set("User-Agent", "exempt_agent") - client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - req := NewRPCReq("123", "eth_foobar", nil) - out, code, err := client.SendBatchRPC(req, req, req) - require.NoError(t, err) - var res []proxyd.RPCRes - require.NoError(t, json.Unmarshal(out, &res)) - - require.Equal(t, 200, code) - require.Equal(t, 3, len(res)) - require.Nil(t, res[0].Error) - require.Nil(t, res[1].Error) - require.Nil(t, res[2].Error) - }) - - time.Sleep(time.Second) - - t.Run("global RPC override", func(t *testing.T) { - h := make(http.Header) - h.Set("User-Agent", "exempt_agent") - client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - limitedRes, codes := spamReqs(t, client, "eth_baz", 429, 2) - // use 1 and 1 here since the limit for eth_baz is 1 - require.Equal(t, 1, codes[429]) - require.Equal(t, 1, codes[200]) - RequireEqualJSON(t, []byte(frontendOverLimitResponseWithID), limitedRes) - }) -} - -func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int, n int) ([]byte, map[int]int) { - resCh := make(chan *resWithCode) - for i := 0; i < n; i++ { - go func() { - res, code, err := client.SendRPC(method, nil) - require.NoError(t, err) - resCh <- &resWithCode{ - code: code, - res: res, - } - }() - } - - codes := make(map[int]int) - var limitedRes []byte - for i := 0; i < n; i++ { - res := <-resCh - code := res.code - if codes[code] == 0 { - codes[code] = 1 - } else { - codes[code] += 1 - } - - if code == limCode { - limitedRes = res.res - } - } - - return limitedRes, codes -}
diff --git OP/proxyd/integration_tests/sender_rate_limit_test.go CELO/proxyd/integration_tests/sender_rate_limit_test.go deleted file mode 100644 index 20c5f0a7cde423749a0e678b260d9ee6175c4598..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/sender_rate_limit_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package integration_tests - -import ( - "bufio" - "fmt" - "math" - "os" - "strings" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" -) - -const txHex1 = "0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6c" + - "d17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1" + - "b2774201bac50f627403eac1b732459cf7000000000000000000000000000000" + - "0000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd6" + - "1145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee" + - "08bfac58f58fb3b8bcef5af98578bdeaddf40bde42" - -const txHex2 = "0x02f8758201a48217fd84773594008504a817c80082520894be53e587975603" + - "a13d0923d0aa6d37c5233dd750865af3107a400080c080a04aefbd5819c35729" + - "138fe26b6ae1783ebf08d249b356c2f920345db97877f3f7a008d5ae92560a3c" + - "65f723439887205713af7ce7d7f6b24fba198f2afa03435867" - -const dummyRes = `{"id": 123, "jsonrpc": "2.0", "result": "dummy"}` - -const limRes = `{"error":{"code":-32017,"message":"sender is over rate limit"},"id":1,"jsonrpc":"2.0"}` - -func TestSenderRateLimitValidation(t *testing.T) { - goodBackend := NewMockBackend(SingleResponseHandler(200, dummyRes)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("sender_rate_limit") - - // Don't perform rate limiting in this test since we're only testing - // validation. - config.SenderRateLimit.Limit = math.MaxInt - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - f, err := os.Open("testdata/testdata.txt") - require.NoError(t, err) - defer f.Close() - - scanner := bufio.NewScanner(f) - scanner.Scan() // skip header - for scanner.Scan() { - record := strings.Split(scanner.Text(), "|") - name, body, expResponseBody := record[0], record[1], record[2] - require.NoError(t, err) - t.Run(name, func(t *testing.T) { - res, _, err := client.SendRequest([]byte(body)) - require.NoError(t, err) - RequireEqualJSON(t, []byte(expResponseBody), res) - }) - } -} - -func TestSenderRateLimitLimiting(t *testing.T) { - goodBackend := NewMockBackend(SingleResponseHandler(200, dummyRes)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("sender_rate_limit") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - // Two separate requests from the same sender - // should be rate limited. - res1, code1, err := client.SendRequest(makeSendRawTransaction(txHex1)) - require.NoError(t, err) - RequireEqualJSON(t, []byte(dummyRes), res1) - require.Equal(t, 200, code1) - res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1)) - require.NoError(t, err) - RequireEqualJSON(t, []byte(limRes), res2) - require.Equal(t, 429, code2) - - // Clear the limiter. - time.Sleep(1100 * time.Millisecond) - - // Two separate requests from different senders - // should not be rate limited. - res1, code1, err = client.SendRequest(makeSendRawTransaction(txHex1)) - require.NoError(t, err) - res2, code2, err = client.SendRequest(makeSendRawTransaction(txHex2)) - require.NoError(t, err) - RequireEqualJSON(t, []byte(dummyRes), res1) - require.Equal(t, 200, code1) - RequireEqualJSON(t, []byte(dummyRes), res2) - require.Equal(t, 200, code2) - - // Clear the limiter. - time.Sleep(1100 * time.Millisecond) - - // A batch request should rate limit within the batch itself. - batch := []byte(fmt.Sprintf( - `[%s, %s, %s]`, - makeSendRawTransaction(txHex1), - makeSendRawTransaction(txHex1), - makeSendRawTransaction(txHex2), - )) - res, code, err := client.SendRequest(batch) - require.NoError(t, err) - require.Equal(t, 200, code) - RequireEqualJSON(t, []byte(fmt.Sprintf( - `[%s, %s, %s]`, - dummyRes, - limRes, - dummyRes, - )), res) -} - -func makeSendRawTransaction(dataHex string) []byte { - return []byte(`{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["` + dataHex + `"],"id":1}`) -}
diff --git OP/proxyd/integration_tests/smoke_test.go CELO/proxyd/integration_tests/smoke_test.go deleted file mode 100644 index 5fed7571bcec3b104f5fb3835bef40174d7eb07b..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/smoke_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package integration_tests - -import ( - "fmt" - "io" - "os" - "strings" - "testing" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/require" -) - -func TestInitProxyd(t *testing.T) { - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("smoke") - - sysStdOut := os.Stdout - r, w, err := os.Pipe() - require.NoError(t, err) - os.Stdout = w - - proxyd.SetLogLevel(log.LevelInfo) - - defer func() { - w.Close() - out, _ := io.ReadAll(r) - require.True(t, strings.Contains(string(out), "started proxyd")) - require.True(t, strings.Contains(string(out), "shutting down proxyd")) - fmt.Println(string(out)) - os.Stdout = sysStdOut - }() - - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - t.Run("initialization", func(t *testing.T) { - client := NewProxydClient("http://127.0.0.1:8545") - res, code, err := client.SendRPC(ethChainID, nil) - require.NoError(t, err) - require.Equal(t, 200, code) - require.NotNil(t, res) - }) - -}
diff --git OP/proxyd/integration_tests/testdata/batch_timeout.toml CELO/proxyd/integration_tests/testdata/batch_timeout.toml deleted file mode 100644 index 4238aafeea2f222e955bd7efd8341da4c6c2ab71..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/batch_timeout.toml +++ /dev/null @@ -1,20 +0,0 @@ -[server] -rpc_port = 8545 -timeout_seconds = 1 -max_upstream_batch_size = 1 - -[backend] -response_timeout_seconds = 1 -max_retries = 3 - -[backends] -[backends.slow] -rpc_url = "$SLOW_BACKEND_RPC_URL" -ws_url = "$SLOW_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["slow"] - -[rpc_method_mappings] -eth_chainId = "main"
diff --git OP/proxyd/integration_tests/testdata/batching.toml CELO/proxyd/integration_tests/testdata/batching.toml deleted file mode 100644 index 476283597537cbc15eaa099c9d7432b77c64b072..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/batching.toml +++ /dev/null @@ -1,23 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" -net_version = "main" -eth_call = "main" - -[batch] -error_message = "over batch size custom message" -max_size = 5 \ No newline at end of file
diff --git OP/proxyd/integration_tests/testdata/caching.toml CELO/proxyd/integration_tests/testdata/caching.toml deleted file mode 100644 index 41bc65b9a785addf8758016fefa9c90f0ab6349b..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/caching.toml +++ /dev/null @@ -1,36 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 - -[redis] -url = "$REDIS_URL" -namespace = "proxyd" - -[cache] -enabled = true - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" -net_version = "main" -eth_getBlockByNumber = "main" -eth_blockNumber = "main" -eth_call = "main" -eth_getBlockTransactionCountByHash = "main" -eth_getUncleCountByBlockHash = "main" -eth_getBlockByHash = "main" -eth_getTransactionByHash = "main" -eth_getTransactionByBlockHashAndIndex = "main" -eth_getUncleByBlockHashAndIndex = "main" -eth_getTransactionReceipt = "main" -debug_getRawReceipts = "main"
diff --git OP/proxyd/integration_tests/testdata/consensus.toml CELO/proxyd/integration_tests/testdata/consensus.toml deleted file mode 100644 index bb130368ea9ccd88c140d49a2e11a969e0af8fa8..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/consensus.toml +++ /dev/null @@ -1,30 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 -max_degraded_latency_threshold = "30ms" - -[backends] -[backends.node1] -rpc_url = "$NODE1_URL" - -[backends.node2] -rpc_url = "$NODE2_URL" - -[backend_groups] -[backend_groups.node] -backends = ["node1", "node2"] -consensus_aware = true -consensus_handler = "noop" # allow more control over the consensus poller for tests -consensus_ban_period = "1m" -consensus_max_update_threshold = "2m" -consensus_max_block_lag = 8 -consensus_min_peer_count = 4 - -[rpc_method_mappings] -eth_call = "node" -eth_chainId = "node" -eth_blockNumber = "node" -eth_getBlockByNumber = "node" -consensus_getReceipts = "node"
diff --git OP/proxyd/integration_tests/testdata/consensus_responses.yml CELO/proxyd/integration_tests/testdata/consensus_responses.yml deleted file mode 100644 index 642c3340f04bc94665d5f3cd3132b415b6e7de85..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/consensus_responses.yml +++ /dev/null @@ -1,234 +0,0 @@ -- method: eth_chainId - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": "hello", - } -- method: net_peerCount - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": "0x10" - } -- method: eth_syncing - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": false - } -- method: eth_getBlockByNumber - block: latest - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x101", - "number": "0x101" - } - } -- method: eth_getBlockByNumber - block: 0x101 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x101", - "number": "0x101" - } - } -- method: eth_getBlockByNumber - block: 0x102 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x102", - "number": "0x102" - } - } -- method: eth_getBlockByNumber - block: 0x103 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x103", - "number": "0x103" - } - } -- method: eth_getBlockByNumber - block: 0x10a - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x10a", - "number": "0x10a" - } - } -- method: eth_getBlockByNumber - block: 0x132 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x132", - "number": "0x132" - } - } -- method: eth_getBlockByNumber - block: 0x133 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x133", - "number": "0x133" - } - } -- method: eth_getBlockByNumber - block: 0x134 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x134", - "number": "0x134" - } - } -- method: eth_getBlockByNumber - block: 0x200 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x200", - "number": "0x200" - } - } -- method: eth_getBlockByNumber - block: 0x91 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0x91", - "number": "0x91" - } - } -- method: eth_getBlockByNumber - block: safe - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0xe1", - "number": "0xe1" - } - } -- method: eth_getBlockByNumber - block: 0xe1 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0xe1", - "number": "0xe1" - } - } -- method: eth_getBlockByNumber - block: finalized - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0xc1", - "number": "0xc1" - } - } -- method: eth_getBlockByNumber - block: 0xc1 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0xc1", - "number": "0xc1" - } - } -- method: eth_getBlockByNumber - block: 0xd1 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash_0xd1", - "number": "0xd1" - } - } -- method: debug_getRawReceipts - block: 0x55 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "_": "debug_getRawReceipts" - } - } -- method: debug_getRawReceipts - block: 0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "_": "debug_getRawReceipts" - } - } -- method: debug_getRawReceipts - block: 0x101 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "_": "debug_getRawReceipts" - } - } -- method: eth_getTransactionReceipt - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "_": "eth_getTransactionReceipt" - } - } -- method: alchemy_getTransactionReceipts - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "_": "alchemy_getTransactionReceipts" - } - }
diff --git OP/proxyd/integration_tests/testdata/failover.toml CELO/proxyd/integration_tests/testdata/failover.toml deleted file mode 100644 index 80ff99045f34d83c739363b648b46f4088fe6f78..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/failover.toml +++ /dev/null @@ -1,20 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" -[backends.bad] -rpc_url = "$BAD_BACKEND_RPC_URL" -ws_url = "$BAD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["bad", "good"] - -[rpc_method_mappings] -eth_chainId = "main" \ No newline at end of file
diff --git OP/proxyd/integration_tests/testdata/fallback.toml CELO/proxyd/integration_tests/testdata/fallback.toml deleted file mode 100644 index c801ca3a898098ce413caf749f02e53d87ada73d..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/fallback.toml +++ /dev/null @@ -1,31 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 -max_degraded_latency_threshold = "30ms" - -[backends] -[backends.normal] -rpc_url = "$NODE1_URL" - -[backends.fallback] -rpc_url = "$NODE2_URL" - -[backend_groups] -[backend_groups.node] -backends = ["normal", "fallback"] -consensus_aware = true -consensus_handler = "noop" # allow more control over the consensus poller for tests -consensus_ban_period = "1m" -consensus_max_update_threshold = "2m" -consensus_max_block_lag = 8 -consensus_min_peer_count = 4 -fallbacks = ["fallback"] - -[rpc_method_mappings] -eth_call = "node" -eth_chainId = "node" -eth_blockNumber = "node" -eth_getBlockByNumber = "node" -consensus_getReceipts = "node"
diff --git OP/proxyd/integration_tests/testdata/frontend_rate_limit.toml CELO/proxyd/integration_tests/testdata/frontend_rate_limit.toml deleted file mode 100644 index 8aa9d19f8f689dc3ff421db0c8b9126df86fe077..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/frontend_rate_limit.toml +++ /dev/null @@ -1,35 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" -eth_foobar = "main" -eth_baz = "main" - -[rate_limit] -base_rate = 2 -base_interval = "1s" -exempt_origins = ["exempt_origin"] -exempt_user_agents = ["exempt_agent"] -error_message = "over rate limit with special message" - -[rate_limit.method_overrides.eth_foobar] -limit = 1 -interval = "1s" - -[rate_limit.method_overrides.eth_baz] -limit = 1 -interval = "1s" -global = true \ No newline at end of file
diff --git OP/proxyd/integration_tests/testdata/max_rpc_conns.toml CELO/proxyd/integration_tests/testdata/max_rpc_conns.toml deleted file mode 100644 index 68d7c19c72ef8df81b57c846c719531e0b507e6d..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/max_rpc_conns.toml +++ /dev/null @@ -1,19 +0,0 @@ -[server] -rpc_port = 8545 -max_concurrent_rpcs = 2 - -[backend] -# this should cover blocked requests due to max_concurrent_rpcs -response_timeout_seconds = 12 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main"
diff --git OP/proxyd/integration_tests/testdata/out_of_service_interval.toml CELO/proxyd/integration_tests/testdata/out_of_service_interval.toml deleted file mode 100644 index 157fa06c18ab55388fae4c5e5c57f8b8adf10d3a..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/out_of_service_interval.toml +++ /dev/null @@ -1,22 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 -max_retries = 1 -out_of_service_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" -[backends.bad] -rpc_url = "$BAD_BACKEND_RPC_URL" -ws_url = "$BAD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["bad", "good"] - -[rpc_method_mappings] -eth_chainId = "main"
diff --git OP/proxyd/integration_tests/testdata/retries.toml CELO/proxyd/integration_tests/testdata/retries.toml deleted file mode 100644 index dc9466dddcf17e2771b1c8e812bc627e2fda899f..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/retries.toml +++ /dev/null @@ -1,18 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 -max_retries = 3 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" \ No newline at end of file
diff --git OP/proxyd/integration_tests/testdata/sender_rate_limit.toml CELO/proxyd/integration_tests/testdata/sender_rate_limit.toml deleted file mode 100644 index c99959d84a914b8858a055c9b3a571819f965490..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/sender_rate_limit.toml +++ /dev/null @@ -1,24 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" -eth_sendRawTransaction = "main" - -[sender_rate_limit] -allowed_chain_ids = [0, 420] # adding 0 allows pre-EIP-155 transactions -enabled = true -interval = "1s" -limit = 1
diff --git OP/proxyd/integration_tests/testdata/size_limits.toml CELO/proxyd/integration_tests/testdata/size_limits.toml deleted file mode 100644 index bd4afab534b9455ce03887a547b515f217b32ea5..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/size_limits.toml +++ /dev/null @@ -1,21 +0,0 @@ -whitelist_error_message = "rpc method is not whitelisted custom message" - -[server] -rpc_port = 8545 -max_request_body_size_bytes = 150 - -[backend] -response_timeout_seconds = 1 -max_response_size_bytes = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" \ No newline at end of file
diff --git OP/proxyd/integration_tests/testdata/smoke.toml CELO/proxyd/integration_tests/testdata/smoke.toml deleted file mode 100644 index a2187a25055d8ba7bcb8904e4617649aec7e50f7..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/smoke.toml +++ /dev/null @@ -1,18 +0,0 @@ -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" -
diff --git OP/proxyd/integration_tests/testdata/testdata.txt CELO/proxyd/integration_tests/testdata/testdata.txt deleted file mode 100644 index 4bdd635a38fc8cd62c349c39ed567f7eb366a750..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/testdata.txt +++ /dev/null @@ -1,14 +0,0 @@ -name|body|responseBody -not json|not json|{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null} -not json-rpc|{"foo":"bar"}|{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null} -missing fields json-rpc|{"jsonrpc":"2.0"}|{"jsonrpc":"2.0","error":{"code":-32600,"message":"no method specified"},"id":null} -bad method json-rpc|{"jsonrpc":"2.0","method":"eth_notSendRawTransaction","id":1}|{"jsonrpc":"2.0","error":{"code":-32601,"message":"rpc method is not whitelisted"},"id":1} -no transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"missing value for required argument 0"},"id":1} -invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf6806872fcc650ad4e77e0629206426cd183d751e9ddcc8d5e77"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"rlp: value size exceeds available input length"},"id":1} -invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x1234"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"transaction type not supported"},"id":1} -valid transaction data - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} -valid transaction data - contract call|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6cd17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1b2774201bac50f627403eac1b732459cf70000000000000000000000000000000000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd61145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} -valid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} -invalid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1} -no chain id (pre eip-155) - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf865808609184e72a00082271094000000000000000000000000000000000000000001001ba0d937ddb66e7788f917864b8e6974cac376b091154db1c25ff8429a6e61016e74a054ced39349e7658b7efceccfabc461e02418eb510124377949cfae8ccf1831af"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} -batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1},{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1},{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32601,"message":"rpc method is not whitelisted"},"id":123}]
diff --git OP/proxyd/integration_tests/testdata/whitelist.toml CELO/proxyd/integration_tests/testdata/whitelist.toml deleted file mode 100644 index 4a65248d7162821ef45b266e576cf0eaf13dbc01..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/whitelist.toml +++ /dev/null @@ -1,19 +0,0 @@ -whitelist_error_message = "rpc method is not whitelisted custom message" - -[server] -rpc_port = 8545 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" \ No newline at end of file
diff --git OP/proxyd/integration_tests/testdata/ws.toml CELO/proxyd/integration_tests/testdata/ws.toml deleted file mode 100644 index 4642e6bc0fc904a3913ef952bea91b5783fc54ea..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/testdata/ws.toml +++ /dev/null @@ -1,28 +0,0 @@ -whitelist_error_message = "rpc method is not whitelisted" - -ws_backend_group = "main" - -ws_method_whitelist = [ - "eth_subscribe", - "eth_accounts" -] - -[server] -rpc_port = 8545 -ws_port = 8546 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" -max_ws_conns = 1 - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main"
diff --git OP/proxyd/integration_tests/util_test.go CELO/proxyd/integration_tests/util_test.go deleted file mode 100644 index 36edce13ef78e65779898fa8f8bb8ad4108d6c5b..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/util_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package integration_tests - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "testing" - "time" - - "github.com/BurntSushi/toml" - "github.com/gorilla/websocket" - "github.com/stretchr/testify/require" - "golang.org/x/exp/slog" - - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/proxyd" -) - -type ProxydHTTPClient struct { - url string - headers http.Header -} - -func NewProxydClient(url string) *ProxydHTTPClient { - return NewProxydClientWithHeaders(url, make(http.Header)) -} - -func NewProxydClientWithHeaders(url string, headers http.Header) *ProxydHTTPClient { - clonedHeaders := headers.Clone() - clonedHeaders.Set("Content-Type", "application/json") - return &ProxydHTTPClient{ - url: url, - headers: clonedHeaders, - } -} - -func (p *ProxydHTTPClient) SendRPC(method string, params []interface{}) ([]byte, int, error) { - rpcReq := NewRPCReq("999", method, params) - body, err := json.Marshal(rpcReq) - if err != nil { - panic(err) - } - return p.SendRequest(body) -} - -func (p *ProxydHTTPClient) SendBatchRPC(reqs ...*proxyd.RPCReq) ([]byte, int, error) { - body, err := json.Marshal(reqs) - if err != nil { - panic(err) - } - return p.SendRequest(body) -} - -func (p *ProxydHTTPClient) SendRequest(body []byte) ([]byte, int, error) { - req, err := http.NewRequest("POST", p.url, bytes.NewReader(body)) - if err != nil { - panic(err) - } - req.Header = p.headers - - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, -1, err - } - defer res.Body.Close() - code := res.StatusCode - resBody, err := io.ReadAll(res.Body) - if err != nil { - panic(err) - } - return resBody, code, nil -} - -func RequireEqualJSON(t *testing.T, expected []byte, actual []byte) { - expJSON := canonicalizeJSON(t, expected) - actJSON := canonicalizeJSON(t, actual) - require.Equal(t, string(expJSON), string(actJSON)) -} - -func canonicalizeJSON(t *testing.T, in []byte) []byte { - var any interface{} - if in[0] == '[' { - any = make([]interface{}, 0) - } else { - any = make(map[string]interface{}) - } - - err := json.Unmarshal(in, &any) - require.NoError(t, err) - out, err := json.Marshal(any) - require.NoError(t, err) - return out -} - -func ReadConfig(name string) *proxyd.Config { - config := new(proxyd.Config) - _, err := toml.DecodeFile(fmt.Sprintf("testdata/%s.toml", name), config) - if err != nil { - panic(err) - } - return config -} - -func NewRPCReq(id string, method string, params []interface{}) *proxyd.RPCReq { - jsonParams, err := json.Marshal(params) - if err != nil { - panic(err) - } - - return &proxyd.RPCReq{ - JSONRPC: proxyd.JSONRPCVersion, - Method: method, - Params: jsonParams, - ID: []byte(id), - } -} - -type ProxydWSClient struct { - conn *websocket.Conn - msgCB ProxydWSClientOnMessage - closeCB ProxydWSClientOnClose -} - -type WSMessage struct { - Type int - Body []byte -} - -type ( - ProxydWSClientOnMessage func(msgType int, data []byte) - ProxydWSClientOnClose func(err error) -) - -func NewProxydWSClient( - url string, - msgCB ProxydWSClientOnMessage, - closeCB ProxydWSClientOnClose, -) (*ProxydWSClient, error) { - conn, _, err := websocket.DefaultDialer.Dial(url, nil) // nolint:bodyclose - if err != nil { - return nil, err - } - - c := &ProxydWSClient{ - conn: conn, - msgCB: msgCB, - closeCB: closeCB, - } - go c.readPump() - return c, nil -} - -func (h *ProxydWSClient) readPump() { - for { - mType, msg, err := h.conn.ReadMessage() - if err != nil { - if h.closeCB != nil { - h.closeCB(err) - } - return - } - if h.msgCB != nil { - h.msgCB(mType, msg) - } - } -} - -func (h *ProxydWSClient) HardClose() { - h.conn.Close() -} - -func (h *ProxydWSClient) SoftClose() error { - return h.WriteMessage(websocket.CloseMessage, nil) -} - -func (h *ProxydWSClient) WriteMessage(msgType int, msg []byte) error { - return h.conn.WriteMessage(msgType, msg) -} - -func (h *ProxydWSClient) WriteControlMessage(msgType int, msg []byte) error { - return h.conn.WriteControl(msgType, msg, time.Now().Add(time.Minute)) -} - -func InitLogger() { - log.SetDefault(log.NewLogger(slog.NewJSONHandler( - os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))) -}
diff --git OP/proxyd/integration_tests/validation_test.go CELO/proxyd/integration_tests/validation_test.go deleted file mode 100644 index 95cfc295b7aa856ea7a04d7de79c452783950eb7..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/validation_test.go +++ /dev/null @@ -1,258 +0,0 @@ -package integration_tests - -import ( - "fmt" - "os" - "strings" - "testing" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/stretchr/testify/require" -) - -const ( - notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32601,"message":"rpc method is not whitelisted custom message"},"id":999}` - parseErrResponse = `{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}` - invalidJSONRPCVersionResponse = `{"error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` - invalidIDResponse = `{"error":{"code":-32600,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` - invalidMethodResponse = `{"error":{"code":-32600,"message":"no method specified"},"id":null,"jsonrpc":"2.0"}` - invalidBatchLenResponse = `{"error":{"code":-32600,"message":"must specify at least one batch call"},"id":null,"jsonrpc":"2.0"}` -) - -func TestSingleRPCValidation(t *testing.T) { - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("whitelist") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - tests := []struct { - name string - body string - res string - code int - }{ - { - "body not JSON", - "this ain't an RPC call", - parseErrResponse, - 400, - }, - { - "body not RPC", - "{\"not\": \"rpc\"}", - invalidJSONRPCVersionResponse, - 400, - }, - { - "body missing RPC ID", - "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23]}", - invalidIDResponse, - 400, - }, - { - "body has array ID", - "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": []}", - invalidIDResponse, - 400, - }, - { - "body has object ID", - "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": {}}", - invalidIDResponse, - 400, - }, - { - "bad method", - "{\"jsonrpc\": \"2.0\", \"method\": 7, \"params\": [42, 23], \"id\": 1}", - parseErrResponse, - 400, - }, - { - "bad JSON-RPC", - "{\"jsonrpc\": \"1.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 1}", - invalidJSONRPCVersionResponse, - 400, - }, - { - "omitted method", - "{\"jsonrpc\": \"2.0\", \"params\": [42, 23], \"id\": 1}", - invalidMethodResponse, - 400, - }, - { - "not whitelisted method", - "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 999}", - notWhitelistedResponse, - 403, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - res, code, err := client.SendRequest([]byte(tt.body)) - require.NoError(t, err) - RequireEqualJSON(t, []byte(tt.res), res) - require.Equal(t, tt.code, code) - require.Equal(t, 0, len(goodBackend.Requests())) - }) - } -} - -func TestBatchRPCValidation(t *testing.T) { - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("whitelist") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - tests := []struct { - name string - body string - res string - code int - reqCount int - }{ - { - "empty batch", - "[]", - invalidBatchLenResponse, - 400, - 0, - }, - { - "bad json", - "[{,]", - parseErrResponse, - 400, - 0, - }, - { - "not object in batch", - "[123]", - asArray(parseErrResponse), - 200, - 0, - }, - { - "body not RPC", - "[{\"not\": \"rpc\"}]", - asArray(invalidJSONRPCVersionResponse), - 200, - 0, - }, - { - "body missing RPC ID", - "[{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23]}]", - asArray(invalidIDResponse), - 200, - 0, - }, - { - "body has array ID", - "[{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": []}]", - asArray(invalidIDResponse), - 200, - 0, - }, - { - "body has object ID", - "[{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": {}}]", - asArray(invalidIDResponse), - 200, - 0, - }, - // this happens because we can't deserialize the method into a non - // string value, and it blows up the parsing for the whole request. - { - "bad method", - "[{\"error\":{\"code\":-32600,\"message\":\"invalid request\"},\"id\":null,\"jsonrpc\":\"2.0\"}]", - asArray(invalidMethodResponse), - 200, - 0, - }, - { - "bad JSON-RPC", - "[{\"jsonrpc\": \"1.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 1}]", - asArray(invalidJSONRPCVersionResponse), - 200, - 0, - }, - { - "omitted method", - "[{\"jsonrpc\": \"2.0\", \"params\": [42, 23], \"id\": 1}]", - asArray(invalidMethodResponse), - 200, - 0, - }, - { - "not whitelisted method", - "[{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 999}]", - asArray(notWhitelistedResponse), - 200, - 0, - }, - { - "mixed", - asArray( - "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\": 999}", - "{\"jsonrpc\": \"2.0\", \"method\": \"eth_chainId\", \"params\": [], \"id\": 123}", - "123", - ), - asArray( - notWhitelistedResponse, - goodResponse, - parseErrResponse, - ), - 200, - 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - res, code, err := client.SendRequest([]byte(tt.body)) - require.NoError(t, err) - RequireEqualJSON(t, []byte(tt.res), res) - require.Equal(t, tt.code, code) - require.Equal(t, tt.reqCount, len(goodBackend.Requests())) - }) - } -} - -func TestSizeLimits(t *testing.T) { - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("size_limits") - client := NewProxydClient("http://127.0.0.1:8545") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - payload := strings.Repeat("barf", 1024*1024) - out, code, err := client.SendRequest([]byte(fmt.Sprintf(`{"jsonrpc": "2.0", "method": "eth_chainId", "params": [%s], "id": 1}`, payload))) - require.NoError(t, err) - require.Equal(t, `{"jsonrpc":"2.0","error":{"code":-32021,"message":"request body too large"},"id":null}`, strings.TrimSpace(string(out))) - require.Equal(t, 413, code) - - // The default response is already over the size limit in size_limits.toml. - out, code, err = client.SendRequest([]byte(`{"jsonrpc": "2.0", "method": "eth_chainId", "params": [], "id": 1}`)) - require.NoError(t, err) - require.Equal(t, `{"jsonrpc":"2.0","error":{"code":-32020,"message":"backend response too large"},"id":1}`, strings.TrimSpace(string(out))) - require.Equal(t, 500, code) -} - -func asArray(in ...string) string { - return "[" + strings.Join(in, ",") + "]" -}
diff --git OP/proxyd/integration_tests/ws_test.go CELO/proxyd/integration_tests/ws_test.go deleted file mode 100644 index d52cfab5cdd7b8e7d19285046a6eecc3ba36d5c6..0000000000000000000000000000000000000000 --- OP/proxyd/integration_tests/ws_test.go +++ /dev/null @@ -1,241 +0,0 @@ -package integration_tests - -import ( - "os" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/proxyd" - "github.com/gorilla/websocket" - "github.com/stretchr/testify/require" -) - -type backendHandler struct { - msgCB atomic.Value - closeCB atomic.Value -} - -func (b *backendHandler) MsgCB(conn *websocket.Conn, msgType int, data []byte) { - cb := b.msgCB.Load() - if cb == nil { - return - } - cb.(MockWSBackendOnMessage)(conn, msgType, data) -} - -func (b *backendHandler) SetMsgCB(cb MockWSBackendOnMessage) { - b.msgCB.Store(cb) -} - -func (b *backendHandler) CloseCB(conn *websocket.Conn, err error) { - cb := b.closeCB.Load() - if cb == nil { - return - } - cb.(MockWSBackendOnClose)(conn, err) -} - -func (b *backendHandler) SetCloseCB(cb MockWSBackendOnClose) { - b.closeCB.Store(cb) -} - -type clientHandler struct { - msgCB atomic.Value -} - -func (c *clientHandler) MsgCB(msgType int, data []byte) { - cb := c.msgCB.Load().(ProxydWSClientOnMessage) - if cb == nil { - return - } - cb(msgType, data) -} - -func (c *clientHandler) SetMsgCB(cb ProxydWSClientOnMessage) { - c.msgCB.Store(cb) -} - -func TestWS(t *testing.T) { - backendHdlr := new(backendHandler) - clientHdlr := new(clientHandler) - - backend := NewMockWSBackend(nil, func(conn *websocket.Conn, msgType int, data []byte) { - backendHdlr.MsgCB(conn, msgType, data) - }, func(conn *websocket.Conn, err error) { - backendHdlr.CloseCB(conn, err) - }) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - - config := ReadConfig("ws") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { - clientHdlr.MsgCB(msgType, data) - }, nil) - defer client.HardClose() - require.NoError(t, err) - defer shutdown() - - tests := []struct { - name string - backendRes string - expRes string - clientReq string - }{ - { - "ok response", - "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xcd0c3e8af590364c09d0fa6a1210faf5\"}", - "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xcd0c3e8af590364c09d0fa6a1210faf5\"}", - "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newHeads\"]}", - }, - { - "garbage backend response", - "gibblegabble", - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32013,\"message\":\"backend returned an invalid response\"},\"id\":null}", - "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newHeads\"]}", - }, - { - "blacklisted RPC", - "}", - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"rpc method is not whitelisted\"},\"id\":1}", - "{\"id\": 1, \"method\": \"eth_whatever\", \"params\": []}", - }, - { - "garbage client request", - "{}", - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"parse error\"},\"id\":null}", - "barf", - }, - { - "invalid client request", - "{}", - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"parse error\"},\"id\":null}", - "{\"jsonrpc\": \"2.0\", \"method\": true}", - }, - { - "eth_accounts", - "{}", - "{\"jsonrpc\":\"2.0\",\"result\":[],\"id\":1}", - "{\"jsonrpc\": \"2.0\", \"method\": \"eth_accounts\", \"id\": 1}", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - timeout := time.NewTicker(10 * time.Second) - doneCh := make(chan struct{}, 1) - backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { - require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(tt.backendRes))) - }) - clientHdlr.SetMsgCB(func(msgType int, data []byte) { - require.Equal(t, tt.expRes, string(data)) - doneCh <- struct{}{} - }) - require.NoError(t, client.WriteMessage( - websocket.TextMessage, - []byte(tt.clientReq), - )) - select { - case <-timeout.C: - t.Fatalf("timed out") - case <-doneCh: - return - } - }) - } -} - -func TestWSClientClosure(t *testing.T) { - backendHdlr := new(backendHandler) - clientHdlr := new(clientHandler) - - backend := NewMockWSBackend(nil, func(conn *websocket.Conn, msgType int, data []byte) { - backendHdlr.MsgCB(conn, msgType, data) - }, func(conn *websocket.Conn, err error) { - backendHdlr.CloseCB(conn, err) - }) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - - config := ReadConfig("ws") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - for _, closeType := range []string{"soft", "hard"} { - t.Run(closeType, func(t *testing.T) { - client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { - clientHdlr.MsgCB(msgType, data) - }, nil) - require.NoError(t, err) - - timeout := time.NewTicker(30 * time.Second) - doneCh := make(chan struct{}, 1) - backendHdlr.SetCloseCB(func(conn *websocket.Conn, err error) { - doneCh <- struct{}{} - }) - - if closeType == "soft" { - require.NoError(t, client.SoftClose()) - } else { - client.HardClose() - } - - select { - case <-timeout.C: - t.Fatalf("timed out") - case <-doneCh: - return - } - }) - } -} - -func TestWSClientExceedReadLimit(t *testing.T) { - backendHdlr := new(backendHandler) - clientHdlr := new(clientHandler) - - backend := NewMockWSBackend(nil, func(conn *websocket.Conn, msgType int, data []byte) { - backendHdlr.MsgCB(conn, msgType, data) - }, func(conn *websocket.Conn, err error) { - backendHdlr.CloseCB(conn, err) - }) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - - config := ReadConfig("ws") - _, shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { - clientHdlr.MsgCB(msgType, data) - }, nil) - require.NoError(t, err) - - closed := false - originalHandler := client.conn.CloseHandler() - client.conn.SetCloseHandler(func(code int, text string) error { - closed = true - return originalHandler(code, text) - }) - - backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { - t.Fatalf("backend should not get the large message") - }) - - payload := strings.Repeat("barf", 1024*1024) - clientReq := "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"" + payload + "\"]}" - err = client.WriteMessage( - websocket.TextMessage, - []byte(clientReq), - ) - require.Error(t, err) - require.True(t, closed) - -}
diff --git OP/proxyd/pkg/avg-sliding-window/sliding.go CELO/proxyd/pkg/avg-sliding-window/sliding.go deleted file mode 100644 index 70c40be2d6dabcf97d3f0d87eb27d2cadfb9098d..0000000000000000000000000000000000000000 --- OP/proxyd/pkg/avg-sliding-window/sliding.go +++ /dev/null @@ -1,188 +0,0 @@ -package avg_sliding_window - -import ( - "sync" - "time" - - lm "github.com/emirpasic/gods/maps/linkedhashmap" -) - -type Clock interface { - Now() time.Time -} - -// DefaultClock provides a clock that gets current time from the system time -type DefaultClock struct{} - -func NewDefaultClock() *DefaultClock { - return &DefaultClock{} -} -func (c DefaultClock) Now() time.Time { - return time.Now() -} - -// AdjustableClock provides a static clock to easily override the system time -type AdjustableClock struct { - now time.Time -} - -func NewAdjustableClock(now time.Time) *AdjustableClock { - return &AdjustableClock{now: now} -} -func (c *AdjustableClock) Now() time.Time { - return c.now -} -func (c *AdjustableClock) Set(now time.Time) { - c.now = now -} - -type bucket struct { - sum float64 - qty uint -} - -// AvgSlidingWindow calculates moving averages efficiently. -// Data points are rounded to nearest bucket of size `bucketSize`, -// and evicted when they are too old based on `windowLength` -type AvgSlidingWindow struct { - mux sync.Mutex - bucketSize time.Duration - windowLength time.Duration - clock Clock - buckets *lm.Map - qty uint - sum float64 -} - -type SlidingWindowOpts func(sw *AvgSlidingWindow) - -func NewSlidingWindow(opts ...SlidingWindowOpts) *AvgSlidingWindow { - sw := &AvgSlidingWindow{ - buckets: lm.New(), - } - for _, opt := range opts { - opt(sw) - } - if sw.bucketSize == 0 { - sw.bucketSize = time.Second - } - if sw.windowLength == 0 { - sw.windowLength = 5 * time.Minute - } - if sw.clock == nil { - sw.clock = NewDefaultClock() - } - return sw -} - -func WithWindowLength(windowLength time.Duration) SlidingWindowOpts { - return func(sw *AvgSlidingWindow) { - sw.windowLength = windowLength - } -} - -func WithBucketSize(bucketSize time.Duration) SlidingWindowOpts { - return func(sw *AvgSlidingWindow) { - sw.bucketSize = bucketSize - } -} - -func WithClock(clock Clock) SlidingWindowOpts { - return func(sw *AvgSlidingWindow) { - sw.clock = clock - } -} - -func (sw *AvgSlidingWindow) inWindow(t time.Time) bool { - now := sw.clock.Now().Round(sw.bucketSize) - windowStart := now.Add(-sw.windowLength) - return windowStart.Before(t) && !t.After(now) -} - -// Add inserts a new data point into the window, with value `val` and the current time -func (sw *AvgSlidingWindow) Add(val float64) { - t := sw.clock.Now() - sw.AddWithTime(t, val) -} - -// Incr is an alias to insert a data point with value float64(1) and the current time -func (sw *AvgSlidingWindow) Incr() { - sw.Add(1) -} - -// AddWithTime inserts a new data point into the window, with value `val` and time `t` -func (sw *AvgSlidingWindow) AddWithTime(t time.Time, val float64) { - sw.advance() - - defer sw.mux.Unlock() - sw.mux.Lock() - - key := t.Round(sw.bucketSize) - if !sw.inWindow(key) { - return - } - - var b *bucket - current, found := sw.buckets.Get(key) - if !found { - b = &bucket{} - } else { - b = current.(*bucket) - } - - // update bucket - bsum := b.sum - b.qty += 1 - b.sum = bsum + val - - // update window - wsum := sw.sum - sw.qty += 1 - sw.sum = wsum - bsum + b.sum - sw.buckets.Put(key, b) -} - -// advance evicts old data points -func (sw *AvgSlidingWindow) advance() { - defer sw.mux.Unlock() - sw.mux.Lock() - now := sw.clock.Now().Round(sw.bucketSize) - windowStart := now.Add(-sw.windowLength) - keys := sw.buckets.Keys() - for _, k := range keys { - if k.(time.Time).After(windowStart) { - break - } - val, _ := sw.buckets.Get(k) - b := val.(*bucket) - sw.buckets.Remove(k) - if sw.buckets.Size() > 0 { - sw.qty -= b.qty - sw.sum = sw.sum - b.sum - } else { - sw.qty = 0 - sw.sum = 0.0 - } - } -} - -// Avg retrieves the current average for the sliding window -func (sw *AvgSlidingWindow) Avg() float64 { - sw.advance() - if sw.qty == 0 { - return 0 - } - return sw.sum / float64(sw.qty) -} - -// Sum retrieves the current sum for the sliding window -func (sw *AvgSlidingWindow) Sum() float64 { - sw.advance() - return sw.sum -} - -// Count retrieves the data point count for the sliding window -func (sw *AvgSlidingWindow) Count() uint { - sw.advance() - return sw.qty -}
diff --git OP/proxyd/pkg/avg-sliding-window/sliding_test.go CELO/proxyd/pkg/avg-sliding-window/sliding_test.go deleted file mode 100644 index 37074dba8061379c3e84829cab57a463a1553f52..0000000000000000000000000000000000000000 --- OP/proxyd/pkg/avg-sliding-window/sliding_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package avg_sliding_window - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestSlidingWindow_AddWithTime_Single(t *testing.T) { - now := ts("2023-04-21 15:04:05") - clock := NewAdjustableClock(now) - - sw := NewSlidingWindow( - WithWindowLength(10*time.Second), - WithBucketSize(time.Second), - WithClock(clock)) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) - require.Equal(t, 5.0, sw.Avg()) - require.Equal(t, 5.0, sw.Sum()) - require.Equal(t, 1, int(sw.Count())) - require.Equal(t, 1, sw.buckets.Size()) - require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) -} - -func TestSlidingWindow_AddWithTime_TwoValues_SameBucket(t *testing.T) { - now := ts("2023-04-21 15:04:05") - clock := NewAdjustableClock(now) - - sw := NewSlidingWindow( - WithWindowLength(10*time.Second), - WithBucketSize(time.Second), - WithClock(clock)) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) - require.Equal(t, 5.0, sw.Avg()) - require.Equal(t, 10.0, sw.Sum()) - require.Equal(t, 2, int(sw.Count())) - require.Equal(t, 1, sw.buckets.Size()) - require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 10.0, sw.buckets.Values()[0].(*bucket).sum) -} - -func TestSlidingWindow_AddWithTime_ThreeValues_SameBucket(t *testing.T) { - now := ts("2023-04-21 15:04:05") - clock := NewAdjustableClock(now) - - sw := NewSlidingWindow( - WithWindowLength(10*time.Second), - WithBucketSize(time.Second), - WithClock(clock)) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 4) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) - require.Equal(t, 5.0, sw.Avg()) - require.Equal(t, 15.0, sw.Sum()) - require.Equal(t, 3, int(sw.Count())) - require.Equal(t, 1, sw.buckets.Size()) - require.Equal(t, 15.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 3, int(sw.buckets.Values()[0].(*bucket).qty)) -} - -func TestSlidingWindow_AddWithTime_ThreeValues_ThreeBuckets(t *testing.T) { - now := ts("2023-04-21 15:04:05") - clock := NewAdjustableClock(now) - - sw := NewSlidingWindow( - WithWindowLength(10*time.Second), - WithBucketSize(time.Second), - WithClock(clock)) - sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) - sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) - require.Equal(t, 5.0, sw.Avg()) - require.Equal(t, 15.0, sw.Sum()) - require.Equal(t, 3, int(sw.Count())) - require.Equal(t, 3, sw.buckets.Size()) - require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) - require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) - require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) -} - -func TestSlidingWindow_AddWithTime_OutWindow(t *testing.T) { - now := ts("2023-04-21 15:04:05") - clock := NewAdjustableClock(now) - - sw := NewSlidingWindow( - WithWindowLength(10*time.Second), - WithBucketSize(time.Second), - WithClock(clock)) - sw.AddWithTime(ts("2023-04-21 15:03:55"), 1000) - sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) - sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) - require.Equal(t, 5.0, sw.Avg()) - require.Equal(t, 15.0, sw.Sum()) - require.Equal(t, 3, int(sw.Count())) - require.Equal(t, 3, sw.buckets.Size()) - require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) - require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) - require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) -} - -func TestSlidingWindow_AdvanceClock(t *testing.T) { - now := ts("2023-04-21 15:04:05") - clock := NewAdjustableClock(now) - - sw := NewSlidingWindow( - WithWindowLength(10*time.Second), - WithBucketSize(time.Second), - WithClock(clock)) - sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) - sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) - require.Equal(t, 5.0, sw.Avg()) - require.Equal(t, 15.0, sw.Sum()) - require.Equal(t, 3, int(sw.Count())) - require.Equal(t, 3, sw.buckets.Size()) - - require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) - require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) - require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) - - // up until 15:04:05 we had 3 buckets - // let's advance the clock to 15:04:11 and the first data point should be evicted - clock.Set(ts("2023-04-21 15:04:11")) - require.Equal(t, 5.5, sw.Avg()) - require.Equal(t, 11.0, sw.Sum()) - require.Equal(t, 2, int(sw.Count())) - require.Equal(t, 2, sw.buckets.Size()) - require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) - require.Equal(t, 6.0, sw.buckets.Values()[1].(*bucket).sum) - - // let's advance the clock to 15:04:12 and another data point should be evicted - clock.Set(ts("2023-04-21 15:04:12")) - require.Equal(t, 6.0, sw.Avg()) - require.Equal(t, 6.0, sw.Sum()) - require.Equal(t, 1, int(sw.Count())) - require.Equal(t, 1, sw.buckets.Size()) - require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 6.0, sw.buckets.Values()[0].(*bucket).sum) - - // let's advance the clock to 15:04:25 and all data point should be evicted - clock.Set(ts("2023-04-21 15:04:25")) - require.Equal(t, 0.0, sw.Avg()) - require.Equal(t, 0.0, sw.Sum()) - require.Equal(t, 0, int(sw.Count())) - require.Equal(t, 0, sw.buckets.Size()) -} - -func TestSlidingWindow_MultipleValPerBucket(t *testing.T) { - now := ts("2023-04-21 15:04:05") - clock := NewAdjustableClock(now) - - sw := NewSlidingWindow( - WithWindowLength(10*time.Second), - WithBucketSize(time.Second), - WithClock(clock)) - sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) - sw.AddWithTime(ts("2023-04-21 15:04:01"), 12) - sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) - sw.AddWithTime(ts("2023-04-21 15:04:02"), 15) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 1) - sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) - require.Equal(t, 6.125, sw.Avg()) - require.Equal(t, 49.0, sw.Sum()) - require.Equal(t, 8, int(sw.Count())) - require.Equal(t, 3, sw.buckets.Size()) - require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 16.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 2, int(sw.buckets.Values()[1].(*bucket).qty)) - require.Equal(t, 20.0, sw.buckets.Values()[1].(*bucket).sum) - require.Equal(t, 4, int(sw.buckets.Values()[2].(*bucket).qty)) - require.Equal(t, 13.0, sw.buckets.Values()[2].(*bucket).sum) - - // up until 15:04:05 we had 3 buckets - // let's advance the clock to 15:04:11 and the first data point should be evicted - clock.Set(ts("2023-04-21 15:04:11")) - require.Equal(t, 5.5, sw.Avg()) - require.Equal(t, 33.0, sw.Sum()) - require.Equal(t, 6, int(sw.Count())) - require.Equal(t, 2, sw.buckets.Size()) - require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 20.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 4, int(sw.buckets.Values()[1].(*bucket).qty)) - require.Equal(t, 13.0, sw.buckets.Values()[1].(*bucket).sum) - - // let's advance the clock to 15:04:12 and another data point should be evicted - clock.Set(ts("2023-04-21 15:04:12")) - require.Equal(t, 3.25, sw.Avg()) - require.Equal(t, 13.0, sw.Sum()) - require.Equal(t, 4, int(sw.Count())) - require.Equal(t, 1, sw.buckets.Size()) - require.Equal(t, 4, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 13.0, sw.buckets.Values()[0].(*bucket).sum) - - // let's advance the clock to 15:04:25 and all data point should be evicted - clock.Set(ts("2023-04-21 15:04:25")) - require.Equal(t, 0.0, sw.Avg()) - require.Equal(t, 0, sw.buckets.Size()) -} - -func TestSlidingWindow_CustomBucket(t *testing.T) { - now := ts("2023-04-21 15:04:05") - clock := NewAdjustableClock(now) - - sw := NewSlidingWindow( - WithWindowLength(30*time.Second), - WithBucketSize(10*time.Second), - WithClock(clock)) - sw.AddWithTime(ts("2023-04-21 15:03:49"), 5) // key: 03:50, sum: 5.0 - sw.AddWithTime(ts("2023-04-21 15:04:02"), 15) // key: 04:00 - sw.AddWithTime(ts("2023-04-21 15:04:03"), 5) // key: 04:00 - sw.AddWithTime(ts("2023-04-21 15:04:04"), 1) // key: 04:00, sum: 21.0 - sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) // key: 04:10, sum: 3.0 - require.Equal(t, 5.8, sw.Avg()) - require.Equal(t, 29.0, sw.Sum()) - require.Equal(t, 5, int(sw.Count())) - require.Equal(t, 3, sw.buckets.Size()) - require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 21.0, sw.buckets.Values()[1].(*bucket).sum) - require.Equal(t, 3, int(sw.buckets.Values()[1].(*bucket).qty)) - require.Equal(t, 3.0, sw.buckets.Values()[2].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) - - // up until 15:04:05 we had 3 buckets - // let's advance the clock to 15:04:21 and the first data point should be evicted - clock.Set(ts("2023-04-21 15:04:21")) - require.Equal(t, 6.0, sw.Avg()) - require.Equal(t, 24.0, sw.Sum()) - require.Equal(t, 4, int(sw.Count())) - require.Equal(t, 2, sw.buckets.Size()) - require.Equal(t, 21.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 3, int(sw.buckets.Values()[0].(*bucket).qty)) - require.Equal(t, 3.0, sw.buckets.Values()[1].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) - - // let's advance the clock to 15:04:32 and another data point should be evicted - clock.Set(ts("2023-04-21 15:04:32")) - require.Equal(t, 3.0, sw.Avg()) - require.Equal(t, 3.0, sw.Sum()) - require.Equal(t, 1, sw.buckets.Size()) - require.Equal(t, 1, int(sw.Count())) - require.Equal(t, 3.0, sw.buckets.Values()[0].(*bucket).sum) - require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) - - // let's advance the clock to 15:04:46 and all data point should be evicted - clock.Set(ts("2023-04-21 15:04:46")) - require.Equal(t, 0.0, sw.Avg()) - require.Equal(t, 0.0, sw.Sum()) - require.Equal(t, 0, int(sw.Count())) - require.Equal(t, 0, sw.buckets.Size()) -} - -// ts is a convenient method that must parse a time.Time from a string in format `"2006-01-02 15:04:05"` -func ts(s string) time.Time { - t, err := time.Parse(time.DateTime, s) - if err != nil { - panic(err) - } - return t -}
diff --git OP/proxyd/tools/mockserver/handler/handler.go CELO/proxyd/tools/mockserver/handler/handler.go deleted file mode 100644 index 0f9bfcad63cb0cc220261941947eac2b5c826180..0000000000000000000000000000000000000000 --- OP/proxyd/tools/mockserver/handler/handler.go +++ /dev/null @@ -1,135 +0,0 @@ -package handler - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "strings" - - "github.com/ethereum-optimism/optimism/proxyd" - - "github.com/gorilla/mux" - "github.com/pkg/errors" - "gopkg.in/yaml.v3" -) - -type MethodTemplate struct { - Method string `yaml:"method"` - Block string `yaml:"block"` - Response string `yaml:"response"` -} - -type MockedHandler struct { - Overrides []*MethodTemplate - Autoload bool - AutoloadFile string -} - -func (mh *MockedHandler) Serve(port int) error { - r := mux.NewRouter() - r.HandleFunc("/", mh.Handler) - http.Handle("/", r) - fmt.Printf("starting server up on :%d serving MockedResponsesFile %s\n", port, mh.AutoloadFile) - err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil) - - if errors.Is(err, http.ErrServerClosed) { - fmt.Printf("server closed\n") - } else if err != nil { - fmt.Printf("error starting server: %s\n", err) - return err - } - return nil -} - -func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { - body, err := io.ReadAll(req.Body) - if err != nil { - fmt.Printf("error reading request: %v\n", err) - } - - var template []*MethodTemplate - if mh.Autoload { - template = append(template, mh.LoadFromFile(mh.AutoloadFile)...) - } - if mh.Overrides != nil { - template = append(template, mh.Overrides...) - } - - batched := proxyd.IsBatch(body) - var requests []map[string]interface{} - if batched { - err = json.Unmarshal(body, &requests) - if err != nil { - fmt.Printf("error reading request: %v\n", err) - } - } else { - var j map[string]interface{} - err = json.Unmarshal(body, &j) - if err != nil { - fmt.Printf("error reading request: %v\n", err) - } - requests = append(requests, j) - } - - var responses []string - for _, r := range requests { - method := r["method"] - block := "" - if method == "eth_getBlockByNumber" || method == "debug_getRawReceipts" { - block = (r["params"].([]interface{})[0]).(string) - } - - var selectedResponse string - for _, r := range template { - if r.Method == method && r.Block == block { - selectedResponse = r.Response - } - } - if selectedResponse != "" { - var rpcRes proxyd.RPCRes - err = json.Unmarshal([]byte(selectedResponse), &rpcRes) - if err != nil { - panic(err) - } - idJson, _ := json.Marshal(r["id"]) - rpcRes.ID = idJson - res, _ := json.Marshal(rpcRes) - responses = append(responses, string(res)) - } - } - - resBody := "" - if batched { - resBody = "[" + strings.Join(responses, ",") + "]" - } else if len(responses) > 0 { - resBody = responses[0] - } - - _, err = fmt.Fprint(w, resBody) - if err != nil { - fmt.Printf("error writing response: %v\n", err) - } -} - -func (mh *MockedHandler) LoadFromFile(file string) []*MethodTemplate { - contents, err := os.ReadFile(file) - if err != nil { - fmt.Printf("error reading MockedResponsesFile: %v\n", err) - } - var template []*MethodTemplate - err = yaml.Unmarshal(contents, &template) - if err != nil { - fmt.Printf("error reading MockedResponsesFile: %v\n", err) - } - return template -} - -func (mh *MockedHandler) AddOverride(template *MethodTemplate) { - mh.Overrides = append(mh.Overrides, template) -} - -func (mh *MockedHandler) ResetOverrides() { - mh.Overrides = make([]*MethodTemplate, 0) -}
diff --git OP/proxyd/tools/mockserver/main.go CELO/proxyd/tools/mockserver/main.go deleted file mode 100644 index a58fc06325ef7f8a8d4e6a777dca1ee29112c96d..0000000000000000000000000000000000000000 --- OP/proxyd/tools/mockserver/main.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path" - "strconv" - - "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" -) - -func main() { - if len(os.Args) < 3 { - fmt.Printf("simply mock a response based on an external text MockedResponsesFile\n") - fmt.Printf("usage: mockserver <port> <MockedResponsesFile.yml>\n") - os.Exit(1) - } - port, _ := strconv.ParseInt(os.Args[1], 10, 32) - dir, _ := os.Getwd() - - h := handler.MockedHandler{ - Autoload: true, - AutoloadFile: path.Join(dir, os.Args[2]), - } - - err := h.Serve(int(port)) - if err != nil { - fmt.Printf("error starting mockserver: %v\n", err) - } -}
diff --git OP/proxyd/tools/mockserver/node1.yml CELO/proxyd/tools/mockserver/node1.yml deleted file mode 100644 index 313c65349f727898bab3eeef540cd1adf70c5f11..0000000000000000000000000000000000000000 --- OP/proxyd/tools/mockserver/node1.yml +++ /dev/null @@ -1,52 +0,0 @@ -- method: eth_getBlockByNumber - block: latest - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash2", - "number": "0x2" - } - } -- method: eth_getBlockByNumber - block: 0x1 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash1", - "number": "0x1" - } - } -- method: eth_getBlockByNumber - block: 0x2 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash2", - "number": "0x2" - } - } -- method: eth_getBlockByNumber - block: 0x3 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash34", - "number": "0x3" - } - } -- method: debug_getRawReceipts - block: 0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff - response: > - {"jsonrpc":"2.0","id":1,"result":[]} -- method: debug_getRawReceipts - block: 0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb - response: > - {"jsonrpc":"2.0","id":1,"result":["0x02f902c10183037ec5b9010000000000000000000000000200000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000400000000000000000008000000000000000000000000000000000000020000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000200000000000000000000000000000000f901b6f8d994297a60578fd0e13076bf06cfeb2bbcd74f2680d2e1a0e5c486bee358a5fff5e4d70dc5fdaaf14806df125ffde843a8c40db608264812b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000282bde1ac0c0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000016573937382e393939393939393939393939395334393600000000000000000000f8d994297a60578fd0e13076bf06cfeb2bbcd74f2680d2e1a01309ab74031e37b46ee8ce9ff667a17a5c69a500a05d167e4c89ad8b0bc40bf9b8a0000000000000000000000000cd28ab95ae80b31255b2258a116cd2c1a371e0f3000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000652d2a700000000000000000000000000000000000000000000000000000000000000016573937382e393939393939393939393939395334393600000000000000000000","0x02f9039f01830645cdb9010000000020000000000000001000000000000000008004000000000000004000000000000000000002000000000000000000000000000000000000000000000000000000a00000000000000000000000200000000000000000000000000000000000000000000000400000000000000000000000000000000000000008000000000000000000004000400800000020000000000000000000000002000000000000000000000000000000200000000000040000000000000000000000001000000100100200000002000000000100000000010000020000000000000000000000000010000000000000000000000000000000000000000000000000000008040000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0e4bac2a8774c733b2c6da6ddf718b53614f36417dfaac90b23d2747d82d2251580f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba04bd009c947444055f7e29f16c227e544387b9de8b571888d37f50becee99d76ea00000000000000000000000000000000000000000000000000000000000000001","0x02f90327018307aa96b9010000000000000000000000000028000000000000000000000000000000400000000000000000000000000000000000000000000000020000000000000000000000000000100000000000000000000000000000000000000000000000000000004000001000000000000000000000000000000000004000000000000000000000000000000000008000000000000000000000000000000000000000000000008000000000021000000000000000000000002040000000000000000000000000000000000000000000000000000000000000008000000000000000001000000000000000000000000000010000000000000000000000000000000000000000004000f9021cf9013c94af4159a80b6cc41ed517db1c453d1ef5c2e4db72f863a05e3c1311ea442664e8b1611bfabef659120ea7a0a2cfc0667700bebc69cbffe1a000000000000000000000000000000000000000000000000000000000000b574ea0eef6d16b5a91e0c5aa2df75fbb865f2be353c5052294a97ee3522d6b1ed674bfb8c00000000000000000000000006bebc4925716945d46f0ec336d5c2564f419682c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000b47d4ece2bd3d6457b9abcde547e332832a6881b5e8697f86a16fd1e3006843062eda71ce901c9f888b5abb4736b9ca9bb36748e000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000652d2a70f8db946bebc4925716945d46f0ec336d5c2564f419682cf842a0ff64905f73a67fb594e0f940a8075a860db489ad991e032f48c81123eb52d60ba000000000000000000000000000000000000000000000000000000000000b574eb88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000034a36c4ece2bd3d6457b9abcde547e332832a6770a0000000000000000000000000000000000000000000000000095094d8053e000000000000000000000000000","0x02f901a70183083234b9010000000000000000040000000000000000000000020000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000040000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000800000000400000001000000000000000000000000000000000000000000000f89df89b94326c977e6efc84e512bb9c30f76e30c160ed06fbf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004281ecf07378ee595c564a59048801330f3084eea0000000000000000000000000cd5d6cadfd3b4c77415859a5d6999f2eea1da5d4a0000000000000000000000000000000000000000000000001158e460913d00000","0x02f9040a0183094189b9010000000000000000000000100000000000000000000000000000000000000000000002000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000008008000000000000000000000000000f902fff902fc94be72771b59f99adc9c07a43295fcada66cb865b9f842a035ee444266c4bfac0b9704ee4fc68807fe38d1e96b299a097442f8169f48d57da00000000000000000000000000000000000000000000000000000000000000027b902a000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000652d2a700000000000000000000000000000000000000000000000000000000000000027000000000000000000000000bc365d43d4761fd03738ebefa5ff3d7ccf8256a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000652e78100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000183635326432363837663661356263626235613934643639370000000000000000000000000000000000000000000000000000000000000000000000000000001836353235626563643837396139613163653935363364643800000000000000000000000000000000000000000000000000000000000000000000000000000000","0x02f9039f01830c0891b9010000000020000000000000001000000000000000008004000000000000004000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000004000000200000000000000000000000000000000000000000000000400000000040000800000000000000000000000008000000000000000040004000400800000020200000000000000000000002000000000000000200000000000000200000000000040000800000000000000000000000000100000000000002000000000100000000000000000000000000000200000000000010000000000000000000000000000000000000000000000000000108000000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0c48bab3c23e29b52ad3d5b5a41b22448c12d59d5f986b479fdcc485cae9e794a80f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba0d6910807a825d1b186bc97aaa5ae83f44bc11533f05f3bd567dd916e5de20de1a00000000000000000000000000000000000000000000000000000000000000001","0x02f9024a01837e0c08b9010000000000800000000000000100000000000000000000000000000000000000000000000000004000000000000200000000000000000000000000000000001000000000000000000000000000000000000000000040000000000040000000000800000000000000000000000000000000000000000000000000000000000000000000000000400020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000f9013ff9013c94c25708eaf0329888c4db0fa5746647ecca92f257f863a09efce6407f8b9dc575789c1e4cc190f025c452249eb8f7913c49958c9a5535dea000000000000000000000000000000000000000000000000000000000652d2a70a0000000000000000000000000a02d09d454861a0ccd2e8518886cdcec37ecdd2cb8c00000000000000000000000000000000000000000000000000000000000000b220000000000000000000000000000000000000000000000000000000000000b220000000000000000000000000000000000000000000000000000000000000b22000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000066372656174650000000000000000000000000000000000000000000000000000","0x02f9039f018380d310b9010000000020000000000000001000000000000000008404000000000000004000000000000000000002000000000000000000000000000000000040000000000000000000000000000000000000000000200000000000000200000000000000000000000000000000400000000000000000000000000000000000000008000000000000000000004000400800000020000000001000000000000002400000000000000000000000000020200010000000040000000000000000000000000000000100000000400002000000000100000000000000000000000000000000000000000010000000000000000000000000000000000040000000000000000008000000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482eba0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482ebb8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482eba0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482ebb8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0f9cacd1bc3956dffcb1a7d556066528a92396aa2e7da07a02904b3bfd886661180f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba00c4d6405faf20e9cb06cdcc5159705b5a4d42ee7ea60a15f20784e82a0b31786a00000000000000000000000000000000000000000000000000000000000000001","0x02f9010901838210d3b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f90c3f018388b358b9010000000000000000000000000000000000000040000000000200000000000004100000000002000000000000000000000000001000000000680000010000000009004020200000000008080008000800000000000000002000000000000000000040040000220000800000002080000800000000000000020000002090000000000000008400000000000000000000000000000000280000000200000000000040000000004000020000000000000000000000000000000000000000000000000000021002000008000000000000000000000000000800000000200100000820001005000000000000000000080000000000000000000000000040000000000000f90b34f89b948bd0e58032e5343c888eba4e72332176fffa7371f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000016345785d8a0000f85894c9b7edc65488bdbb428526b03935090aef40ff03e1a0df21c415b78ed2552cc9971249e32a053abce6087a0ae0fbf3f78db5174a3493a0000000000000000000000000000000000000000000000000000098b591d3ac0ef8d9946f3a314c1279148e53f51af154817c3ef2c827b1e1a0b0c632f55f1e1b3b2c3d82f41ee4716bb4c00f0f5d84cdafc141581bb8757a4fb8a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000022000100000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000000000000000000000000000000000000000f8d99436ebea3941907c438ca8ca2b1065deef21ccdaede1a04e41ee13e03cd5e0446487b524fdc48af6acf26c074dacdbdfb6b574b42c8146b8a0000000000000000000000000000000000000000000000000000000000000277a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000011f66d6864fcce84d5000e1e981dd586766339490000000000000000000000000000000000000000000000000000213ae829dcc5f9019a946f3a314c1279148e53f51af154817c3ef2c827b1e1a0e9bded5f24a4168e4f3bf44e00298c993b22376aad8c58c7dda9718a54cbea82b90160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001140000000000000d54278911f66d6864fcce84d5000e1e981dd58676633949277ab92de63eb7d8a652bf80385906812f92d49c5139000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000a07355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a00000000000000000000000000000000000000000000000000000000000000144ac853547fa1fe3f4690e452b904578f7d46a531000000000000000000000000000000000000000000000000f9013d9411f66d6864fcce84d5000e1e981dd58676633949f884a0aae74fdfb502b568e3ca6f5aa448a255c90a2f24c4a6104d65ae45f097b37388a00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000277ab8a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000d540000000000000000000000000000000000000000000000000000000000000028b92de63eb7d8a652bf80385906812f92d49c513911f66d6864fcce84d5000e1e981dd58676633949000000000000000000000000000000000000000000000000f85894c9b7edc65488bdbb428526b03935090aef40ff03e1a0df21c415b78ed2552cc9971249e32a053abce6087a0ae0fbf3f78db5174a3493a00000000000000000000000000000000000000000000000000000381ece38c000f8d9946f3a314c1279148e53f51af154817c3ef2c827b1e1a0b0c632f55f1e1b3b2c3d82f41ee4716bb4c00f0f5d84cdafc141581bb8757a4fb8a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000022000100000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000000000000000000000000000000000000000f8d99436ebea3941907c438ca8ca2b1065deef21ccdaede1a04e41ee13e03cd5e0446487b524fdc48af6acf26c074dacdbdfb6b574b42c8146b8a0000000000000000000000000000000000000000000000000000000000000279f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000011f66d6864fcce84d5000e1e981dd586766339490000000000000000000000000000000000000000000000000000d027724dc000f9019a946f3a314c1279148e53f51af154817c3ef2c827b1e1a0e9bded5f24a4168e4f3bf44e00298c993b22376aad8c58c7dda9718a54cbea82b9016000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000114000000000000ac19278911f66d6864fcce84d5000e1e981dd58676633949279f4c11ccee50b70daa47c41849e45316d975b26102000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000a07355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a00000000000000000000000000000000000000000000000000000000000000144ac853547fa1fe3f4690e452b904578f7d46a531000000000000000000000000000000000000000000000000f9013d9411f66d6864fcce84d5000e1e981dd58676633949f884a0aae74fdfb502b568e3ca6f5aa448a255c90a2f24c4a6104d65ae45f097b37388a00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000279fb8a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000ac1900000000000000000000000000000000000000000000000000000000000000284c11ccee50b70daa47c41849e45316d975b2610211f66d6864fcce84d5000e1e981dd58676633949000000000000000000000000000000000000000000000000f8bb94593be683204ff3501e6e4851956a2da310e393b6f842a0c1e18b2a3583b6bc0879a3f3f657c41adfb512076938208ec0b389d6d19874cba00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531b8607355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a","0x02f901a7018389386bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000010000000000000000000000000000020000000000000000000800000000000000000000000010000000000000004000000000000000000000000000000000000000000000000000000000000000000000100400000000000000000000000000000400000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000001000000000000000000000000000000000000000000000000f89df89b946199f797b524166122f1c6e6a78ff321389bb686f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e43f4840dad185beef6daa8e7328b521e6a1a2a0a000000000000000000000000000000000000000000000d3c21bcecceda1000000","0x02f908fa01838cb5bdb90100000000004000000000040000000000000000000000005000000022000004000000000000000000000000000000000000000000000000000000000200002000000000000000080000000000080000000000000400004000000000004000000000000000000002010000000000000000000000000008000000000000100000000000000000000000000000000000001000000000000800000000000000200000000a0000000000000000000000100000000000000000080000000000000000000000000002000000000000000000000000200240000000800000000000000000000010000000040000000000000000000000000000000800000000000000000000f907eff9025d9400000000000000adc04c56bf30ac9d3c0aaf14dcf863a09d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a0000000000000000000000000ea2b4e7f02b859305093f9f4778a19d66ca176d5b901e0392f9c488837a9a75f4b5a139bed7757ebec5091b7e0b6f1ce70cbbbc75050e50000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c0490000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d3664b5e72b46eaba722ab6f43c22dbf4018195400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e1a300000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000002715ccea428f8c7694f7e78b2c89cb454c5f7294000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c68af0bb140000000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845f9025d9400000000000000adc04c56bf30ac9d3c0aaf14dcf863a09d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a0000000000000000000000000ea2b4e7f02b859305093f9f4778a19d66ca176d5b901e0ea28a862f3530833f82dc3d97407cf3a23fb593335f2b11f9ade8d62576f14fe0000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c04900000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000002715ccea428f8c7694f7e78b2c89cb454c5f7294000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d3664b5e72b46eaba722ab6f43c22dbf4018195400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e1a3000000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049f8b99400000000000000adc04c56bf30ac9d3c0aaf14dce1a04b9f2d36e1b4c93de62cc077b00b1a91d84b6c31b4a14e012718dcca230689e7b88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002392f9c488837a9a75f4b5a139bed7757ebec5091b7e0b6f1ce70cbbbc75050e5ea28a862f3530833f82dc3d97407cf3a23fb593335f2b11f9ade8d62576f14fef89b94d3664b5e72b46eaba722ab6f43c22dbf40181954f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a000000000000000000000000000000000000000adc04c56bf30ac9d3c0aaf14dca00000000000000000000000000000000000000000000000000000000017d78400f89b94d3664b5e72b46eaba722ab6f43c22dbf40181954f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a00000000000000000000000000000000000000000000000000000000011e1a300f89b942715ccea428f8c7694f7e78b2c89cb454c5f7294f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a000000000000000000000000000000000000000adc04c56bf30ac9d3c0aaf14dca0000000000000000000000000000000000000000000000035a66e2580ecd70000f89b942715ccea428f8c7694f7e78b2c89cb454c5f7294f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a000000000000000000000000000000000000000000000000002c68af0bb140000","0x02f9036101838ed0a2b9010000000000000000080000000000000000000000000040000000000008000000000000000000010000002000008000000000004000000000000000000000000000000000000000000001000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000010000000000000000048000000000000000000000000000000400000000000000000000000000000010000000000000000008000000000000000000000000000000000000000020002000000000000000000000000008000000000000000000000000000000000800000000000000000000000008000000000000000000000000000000000f90256f89b94eea85fdf0b05d1e0107a61b4b4db1f345854b952f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000030f7fd07ce431d090f8ec59c3b635a4df42a00a1a0000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c21a00000000000000000000000000000000000000000000000012f9aa3647286b60ff89b94fb7378d0997b0092be6bbf278ca9b8058c24752ff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c21a000000000000000000000000030f7fd07ce431d090f8ec59c3b635a4df42a00a1a0000000000000000000000000000000000000000000000001314fb37062980000f901199430f7fd07ce431d090f8ec59c3b635a4df42a00a1e1a03b841dc9ab51e3104bda4f61b41e4271192d22cd19da5ee6e292dc8e2744f713b8e00000000000000000000000009563fdb01bfbf3d6c548c2c64e446cb5900aca88000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001314fb370629800000000000000000000000000000000000000000000000000012f9aa3647286b60fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8","0x02f905c40183904383b9010000000000000000000008000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000010000000000000000000000000100000020002000000020000000800000000000000000000000200000000000000010000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000800000000000006000000000000800000100000000000000000000000000000000000000000000001000000000020000000000000000000800000000800000000000000000000004000000000000000f904b9f901ba94db249fda431b6385ad5e028f3aa31f3f51ebaef2e1a0cdb9fb741d82c65a081bb855b5e42174193549c537fd57a199609593827cff71b90180000000000000000000000000762f1119123806fc0aa4c58f61a9da096910200b0000000000000000000000000000000000000000000000000000000000004443000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000c7a6b4554482e676f65726c690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d0c7a6b4554482e676f65726c693f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e74336636746737656772717266703676720080c6a47e8d030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f9019a94762f1119123806fc0aa4c58f61a9da096910200be1a08636abd6d0e464fe725a13346c7ac779b73561c705506044a2e6b2cdb1295ea5b901600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000532e91ca086964251519359271b99bd08427314f000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000000000c7a6b4554482e676f65726c690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e743366367467376567727172667036767200f9015c94532e91ca086964251519359271b99bd08427314ff863a0fed162bc92844d868aeee15dc79af2ef4aac1ef57805f4eec865983f4d35efd9a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000660aba6ca934d1537a95cc4454469a1026ed6252b8e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000003f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e743366367467376567727172667036767200","0x02f901a7018390a504b9010000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000100000004000000000000000000000000200000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000020000000000000000000000000000000000000000000000000020000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000400000000000001000000000000000000000000000000000000f89df89b94fad6367e97217cc51b4cd838cc086831f81d38c2f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a000000000000000000000000028cd70b79775193ea6f75f0808870b0b9eb1ad01a00000000000000000000000001c5d511f65c99ff3ea07dcca7ed91146ed5351e2a00000000000000000000000000000000000000000000000000000000000000000","0x02f909ad018396c005b901000000400000040000000000000000000000000000000000000000000000000000000000000000000000000000000010000040000000000200000000004020000000000000a0080000080000080000000000000000020000000000800000000000000000000000000000000000000000000000000400000000000000188000000000000000000000001000000000000000010010000020000000000000000000000a0001000000001001000040000000400000000000001000000000000000000000000002000000000000000002000000010000800000000000000000000000000490100000000000000000000001000004000000000010000000000000000000f908a2f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a00000000000000000000000000000000000000000000000000000f4312ed20263f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3b84000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000003d0900f8fc9464046eaf582638f26ba3ff17ea51705f1367cbc3f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000ffffffffa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5fab88000000000000000000000000000000000000000000000000000000000000120f200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000003782dace9d900000ffffffffffffffffffffffffffffffffffffffffffffffffffd46bd89546914bf89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a00000000000000000000000000000000000000000000000000000f4312e94f963f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3b84000000000000000000000000000000000000000000000000000000000658e7c8000000000000000000000000000000000000000000000000000000000003d0900f8fc9464046eaf582638f26ba3ff17ea51705f1367cbc3f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000658e7c80a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5fab8800000000000000000000000000000000000000000000000000000000000011e9a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d900000000000000000000000000000000000000000000000000000002e45f9fa00bf77","0x02f9055c01839a4ac9b90100000000000004000000000000000000000000000000000000000000000000000000000004000000000000000000001000004000000000020000000000402000000000000080080000000000080000000000000000020000000000800000000000000000000000000000000000000000000000000400000000000000100000000000000000000000001000040000000000010010000000000000000000000000400a0800000000001001000040000000400000000000001000000000000004000000008002000000000000000000000100004000000000000000000000010000000090100000000000000000000001000004000000000010000000000000000000f90451f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a0000000000000000000000000000000000000000000000000000000003e7f2450f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a0000000000000000000000000f1675ffed677b2e7ee0c87574ebd279049d9ebf9a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000f1675ffed677b2e7ee0c87574ebd279049d9ebf9b84000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000003d0900f8fc94f1675ffed677b2e7ee0c87574ebd279049d9ebf9f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000ffffffffa000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54b880ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d900000fffffffffffffffffffffffffffffffffffffffffffffff3d3b3ce837117c4f3","0x02f9032701839baf92b9010000000000000000000000000000000000000000000000000000000000400000000000000000000000000200000000000000000000020000001200000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000021000000000000000000000002440000000000000000000000000000000001000000000000000000000000000008000000000000000001000000000000000000000000000000000000008000000000000000000000000000000004000f9021cf9013c94af4159a80b6cc41ed517db1c453d1ef5c2e4db72f863a05e3c1311ea442664e8b1611bfabef659120ea7a0a2cfc0667700bebc69cbffe1a000000000000000000000000000000000000000000000000000000000000b574fa0c16ec891aba6a3f7b381a53cdd85e9d84741f080b35012c1f376edcac6e95f10b8c00000000000000000000000006bebc4925716945d46f0ec336d5c2564f419682c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000f5ce611c7321438f8a30aab16852d68da4f5ab5a4de176e8c0279273cbe8a9919b029628c5cc2def297494bb2b1d1a470ae6ce18000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000652d2a70f8db946bebc4925716945d46f0ec336d5c2564f419682cf842a0ff64905f73a67fb594e0f940a8075a860db489ad991e032f48c81123eb52d60ba000000000000000000000000000000000000000000000000000000000000b574fb88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000034e4bd611c7321438f8a30aab16852d68da4f59a490000000000000000000000000000000000000000000000000429d069189e0000000000000000000000000000","0x02f901ff01839ceb65b9010000000000010002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000020000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894d5c325d183c592c94998000c5e0eed9e6655c020e1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a0c9afd20a1f61bb629588d82d72e13914812259d13161284b98747c955d62317ef89994d5c325d183c592c94998000c5e0eed9e6655c020e1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600336df0b24cf3302afadbc4c828662c60c2cdae602e59beaab7011bf3fd5a223000000000000000000000000000000000000000000000000000000000004d0e5020abc24e9cfc67a97e463deef1ab573cb11d9cd618bedee85c2df85fac6faa2","0x02f901ff01839e2744b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a035ceccc734a10ae9c593304774276b4b6324223b019c38c6662ab923022b2c8af89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb860022db625db05534a27ea14675f8d22d7c603e0981ceff2b02504791b75c4f56100000000000000000000000000000000000000000000000000000000000d7c61019e776def80129482e5f6282056f7318bedf7bc30a4ae7cdf8814d8d0b28a99","0x02f901ff01839f6317b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a01331bfa0165c171aefba1e6eebd55d52bc2389726c1d0594f4a9e2f7812b42e4f89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb86002e2bf007e02d68956b61be94c5661c8c8d1fb7ae552a0b2e7f3869d20d7aa0700000000000000000000000000000000000000000000000000000000000d7c620467725c6a4525d13c195701bf88f4f3c65329438cf8a437c928067d9a80556e","0x02f9033f0183a10e1fb9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000040000040000000000000000200000000000000000000002000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000020000000000004000000000000000080000000200400000000000800000000000000000000000000000000000000000000000000000000000000f90234f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a074c803dcb95e04990c3e71db65899a9e0282003a967632e13eb6ce45d598fb45f9013c94de29d060d45901fb19ed6c6e959eb22d8626708ef863a04264ac208b5fde633ccdd42e0f12c3d6d443a4f3779bbf886925b94665b63a22a0073314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82a0000000000000000000000000c3511006c04ef1d78af4c8e0e74ec18a6e64ff9eb8c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f36e2be14d65c7a832d1a863ecfa06c6730634700000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000000000000000f89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600427eee54195a8dc4d35b17e5cbc8e18b6e8eebdaf2017f6e12dac62232ca9e000000000000000000000000000000000000000000000000000000000000d7c6300b1121da2e28ad38fb1736373bb7fe4787b73c881995bbd391b17aa113a2cc8","0x02f901ff0183a249e6b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a0d4d6f16309b51be964a0482c4cd816fadeaa8759216dd9bbc20773ba36c75ccaf89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600001d0bd72454b02cd7386951338297e0da0f5a8c1076a24c1425928dce0838300000000000000000000000000000000000000000000000000000000000d7c64028c033938806df04fcc792f72f91ca04c3c20963eec03addb44fecf6fa795de","0x02f901090183a29beeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9041e0183a8526db9010000000200000000400000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000010000020000000000000000000000000000004000000080000000004000000002000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800200010000000000000000000000000000000000000000000000000000000040000000000000000f90313f8b994caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8e1a0e2db1e7820b0cca1226a7e7d5cc2a3df28542b04da0f0aa7949f2a74519ef5a0b880000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d310000000000000000000000000000000000000000000000000000b588ff18e000000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d310000000000000000000000000000000000000000000000000000000000000000f87994caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8e1a0305bf06329ff886b42ab3ed2979092b17d3a7fc67e7de42ee393a24c8e39fee7b840000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31f901da9475d8ec64bf68b364b1f45249774e78df2f401399e1a0c6cb9661759518374091eb98266dd634614ae793b31549daff33e83d6dee0165b901a0000000000000000000000000caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004a1c82542ebdb854ece6ce5355b5c48eb299ecd8000000000000000000000000000000000000000000000000000aa87bee53800000000000000000000000000000000000000000000000000000000000000003840000000000000000000000000000000000000000000000000000b588ff18e000000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000001e347468207465737420636f6c6c656374697665206f6e203136204f6374200000","0x02f908450183ab3259b9010000000000004048000010000004005000000000080000000000000000000000080002000000000000008200000004008450000000000000000000100000200200000001000000000040000008200000000000000000000000000000102000000100000000000000000000000480000000000000000000000000000010000000000200000000024000000000000000000010000000000000000001004200400000020000000000000000000000001400000000000000000000040000000200000000000002000000080000000000020000000000000000040000000000000000001010000000000000000000000000000000000000000000000000800000000000f9073af89b944031bc992179a7742bb99ec99da67f852c11927af863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4fa00000000000000000000000000000000000000000000000000000000000000000f89b944031bc992179a7742bb99ec99da67f852c11927af863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4fa000000000000000000000000000000000000000000000000000000000000003e8f902de94d9005878cb1a830355dbf4d814a835c54022038ef884a04b388aecf9fa6cc92253704e5975a6129a4f735bdbd99567df4ed0094ee4ceb5a00000000000000000000000000ded20eaea674409ccfeca298385f361ad359a43a00000000000000000000000004200000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000170db9024000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000652d2a7000000000000000000000000000000000000000000000000000000000000001a4cbd4ece90000000000000000000000004200000000000000000000000000000000000010000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000170d00000000000000000000000000000000000000000000000000000000000000e4662a633a0000000000000000000000003c3a81e81dc49a522a592e7622a7e711c06bf354000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f901fc94fcdc20eaea674409ccfeca298385f361ad358932f842a0cb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52aa00000000000000000000000004200000000000000000000000000000000000010b901a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000170d00000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000e4662a633a0000000000000000000000003c3a81e81dc49a522a592e7622a7e711c06bf354000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f9011d94d029d527e1d700c549f4e04662035b8a8624ce4ff884a0718594027abd4eaed59f95162563e0cc6d0e8d5b86b1c7be8b1b0ac3343d0396a00000000000000000000000004031bc992179a7742bb99ec99da67f852c11927aa0000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000a0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783b880000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000","0xf901090183ab8461b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf901090183abd669b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf903640183ae0bfcb9010000000000100000400000000000000000000000000000084000000000000000000000000000000000000000000000020000000000000000000000000000000000000040008800000000100008000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000010000000000002000000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000020080000000000000040000000080040002000000000000000000000000000000000000000000010000000020000000000000200000000000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000009f40916d0dfb2f8f5fb63d8f76826d09041f2eaeb8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000d68efdc6e047f712f4bff82fc800f5309ca0c4e8e7ba32b255b212fef748ed80af200000000000000000000000000000000000000000000000000000000000000148a555e4fc287650f5e8ca1778a35dd44e893d6aa000000000000000000000000f89b949f40916d0dfb2f8f5fb63d8f76826d09041f2eaef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000363413eb82ff4ebda8e70f8e0f9615b684d2f9eda00000000000000000000000000000000000000000000000000de0b6b3a7640000f89b949f40916d0dfb2f8f5fb63d8f76826d09041f2eaef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000363413eb82ff4ebda8e70f8e0f9615b684d2f9eda00000000000000000000000000000000000000000000000000de0b6b3a7640000","0xf903640183b08524b9010000000000002000000000000000000000000000000040000000000000000000000000000000000020000000000000000000000000000000000000000000000000000040000800000000000008000000000001000000000000000000000000000000000000020000000000000000000800000000000020000000000010000000000002004000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000020080000100400000040000000080100002000000000000000000000000000000000000000000010000000020000000000000200000000000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001a400c8fb05948cf6527abe1b570154f613139797c9cf1ace53bd3c8f4a8f756a703f60000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000005805420c76fdc3d1f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea000000000000000000000000095ac38e1c9af5a6d868c1b376e7c0ba9b9251ca5a00000000000000000000000000000000000000000000000005805420c76fdc3d1","0xf903640183b2fe40b9010000000000002000000000000000000000000000000040000000000000000000000000000000000000000000010000000000000000000000000000000000000000000040000800000000000008000000000001000000000100000000000000000000000000020000000000000000000800000000000020000000000010000000000002000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000020080000100000000040000000080100002000000000000000000000000000000000000000000010000000020000000000000200000008000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001a400d1b36efb5ef48c1953b391dadade996e546aebdeea81b118ac2eefda2a548bac50000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000005735cbdc26b5d43af89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000f7db1e439db7a79a8f91cbde7c0069a97b89c813a00000000000000000000000000000000000000000000000005735cbdc26b5d43a","0xf902c40183b4a7bab9010000000040000000080000000000000000000000000000000001000002000000000000000000000000000000000000000000000000000000000000000000002000000040000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000008000000000000000000000000000010000000000000000000000000000000000002000000000000000000000000000000000000000000200000000000000000000000000000040000000080000020000000000000000000000000000000000000000000010000000000000000000000200000000000000000000000000000000000000000000000000000f901b9f8dc94980205d352f198748b626f6f7c38a8a5663ec981f863a074bbc026808dcba59692d6a8bb20596849ca718e10e2432c6cdf48af865bc5d9a0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000a6bf2be6c60175601bf88217c75dd4b14abb5fbbb860312401a801d73bde5cacb7e2eeba637d09c23f9a18dff6504cbdc8f7d7b2e348312401a801d73bde5cacb7e2eeba637d09c23f9a18dff6504cbdc8f7d7b2e3480000000000000000000000000000000000000000000000000000000000000014f8d994a6bf2be6c60175601bf88217c75dd4b14abb5fbbe1a0293e3a2153dc5c8d3667cbd6ede71a71674b2381e5dc4b40c91ad0e813447c0fb8a0000000000000000000000000980205d352f198748b626f6f7c38a8a5663ec981cf507db63af850463ebaaeffe5f783680e6aa364627d07c6488f5ba313ebbcca000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000","0xf903640183b6bf22b9010000000000002000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000008000000000001000000000000000000000000000000000000020000000000000000000800000000000020000000000010000000008002002000000000000000000000000000000000000002000000000000000000000000000000000040000000000000000000020180000100000000040000000000140002000000000000000000000000000000000000000000000000000020000000000000200000000000000000000000000000001002000000000400000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006fa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000073bd808065379db49bfa5292e83432fd6ede06ae24792f0d266ac89be03f7925229ce0000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000a7da3311d85502df89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006fa0000000000000000000000000c677f78297d40138b68be496ae34d861dc9edbeba00000000000000000000000000000000000000000000000000a7da3311d85502d","0xf902c40183b86890b9010000000040000000080000000000000000000000000000000001000002000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000008000000000000000000000000000010000000000000000000000000000000000002000000000000000000000000000000000040000000200000000000000100000000000000040000000000040020000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000f901b9f8dc94980205d352f198748b626f6f7c38a8a5663ec981f863a074bbc026808dcba59692d6a8bb20596849ca718e10e2432c6cdf48af865bc5d9a0000000000000000000000000000000000000000000000000000000000000006fa0000000000000000000000000a6bf2be6c60175601bf88217c75dd4b14abb5fbbb86031f7494a39416159f52b17758dda7bdafa4215dcf706dfc06cd6f8933b817d2031f7494a39416159f52b17758dda7bdafa4215dcf706dfc06cd6f8933b817d200000000000000000000000000000000000000000000000000000000000000014f8d994a6bf2be6c60175601bf88217c75dd4b14abb5fbbe1a0293e3a2153dc5c8d3667cbd6ede71a71674b2381e5dc4b40c91ad0e813447c0fb8a0000000000000000000000000980205d352f198748b626f6f7c38a8a5663ec981ea46511277ad0d9d47ff09d07eb1e1298bcefa6a6da798de1e0af884002aceb1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000","0x02f902460183e60720b9010000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008002000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000001000000000000080004002000000000000000000000000000000000000000100000000000020000000000000001000000000000000000000000000000000020000000000000000f9013bf89b9488045945952b374abf696602941b51149bad8ab4f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa75a00000000000000000000000000000000000000000033b2e3c9fd0803ce8000000f89c9488045945952b374abf696602941b51149bad8ab4f884a02f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0da00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa75a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa7580","0xf901090183e65928b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183e6f1e1b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99416324d80bfc68b1fec6c288f0dac640a044d2678e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000180ab5200000000000000000000000000000000000000000000000000000000652d2a5500000000000000000000000000000000000000000000000000000000000000074144412f55534400000000000000000000000000000000000000000000000000","0x02f901e50183e78ab2b9010000000000000000000000000400000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000800040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994e7a43467520e4d12d1f9e94b99d6f041786aadcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024cba3965500000000000000000000000000000000000000000000000000000000652d2a550000000000000000000000000000000000000000000000000000000000000008574554482f555344000000000000000000000000000000000000000000000000","0x02f901e50183e82377b9010000000000000000000000000000000000000000000000000000000000000000000000080000000000001000000020000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994b52a8b962ff3d8a6a0937896ff3da3879eac64e3e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005f6321e00000000000000000000000000000000000000000000000000000000652d2a560000000000000000000000000000000000000000000000000000000000000008555344542f555344000000000000000000000000000000000000000000000000","0xf901090183e8ef8fb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183e9883cb9010000000002000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000f8dbf8d994117a5ab00f93469bea455f0864ef9ad8d9630cc9e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000007e060100000000000000000000000000000000000000000000000000000000652d2a5800000000000000000000000000000000000000000000000000000000000000074752542f55534400000000000000000000000000000000000000000000000000","0x02f901e50183ea2101b9010000000000000000000000000000000000000000000000000000004000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000800000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994bbbf9614de2b788a66d970b552a79fae6419abdce1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005eebd8700000000000000000000000000000000000000000000000000000000652d2a580000000000000000000000000000000000000000000000000000000000000008465241582f555344000000000000000000000000000000000000000000000000","0x02f901e50183eab9d2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200f8dbf8d99439f46d72bb20c7bcb8a2cdf52630fac1496e859ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024f8bc612d00000000000000000000000000000000000000000000000000000000652d2a5a0000000000000000000000000000000000000000000000000000000000000008574554482f555344000000000000000000000000000000000000000000000000","0x02f901e50183eb528bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000020000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9945ae58e9dec27619572a42dad916e413afa89e46de1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000019d1f00000000000000000000000000000000000000000000000000000000652d2a5c000000000000000000000000000000000000000000000000000000000000000842414e4b2f555344000000000000000000000000000000000000000000000000","0x02f9043a0183ed1237b9010000200000000000000000002080000000000000000000000000010000000000000000000000000000000000000000200000000000000000000000020000000000000000000400000000002008000000300000000000000000000000008000000000000000008000100000000000000000000000000000000000020010000000000000000080000000004000000000000000000001000000080000104000000000000000000000000000000000000000000000000000000000000000000000000000204002000000001000000000000000000000000000001000000000000020000000000000000000000000000000000000000000000001400000000000000000f9032ff87a94b4fbf271143f4fbf7b91a5ded31805e42b2208d6f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000000000000000000000000000000000e8d4a51000f89b94b4fbf271143f4fbf7b91a5ded31805e42b2208d6f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000077f0ea26bae49c9f412a1511730c7c1d3382f697a0000000000000000000000000000000000000000000000000000000e8d4a51000f89b94257c2c98163227062e5a33095a20fc7604ee52b5f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000077f0ea26bae49c9f412a1511730c7c1d3382f697a0000000000000000000000000185d901fe591ce516ed9e192b33da3ef14d53b93a0000000000000000000000000000000000000000000000000042a7d29b88844e5f8799477f0ea26bae49c9f412a1511730c7c1d3382f697e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000007b61de650eaa9bccab150000000000000000000000000000000000000000000000001adafd27de2bd68bf8fc9477f0ea26bae49c9f412a1511730c7c1d3382f697f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000185d901fe591ce516ed9e192b33da3ef14d53b93b8800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e8d4a51000000000000000000000000000000000000000000000000000042a7d29b88844e50000000000000000000000000000000000000000000000000000000000000000","0x02f901e50183edaafcb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000800000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994aac02884a376dc5145389ba37f08b0dde08d3f18e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000b37b80500000000000000000000000000000000000000000000000000000000652d2a5e0000000000000000000000000000000000000000000000000000000000000008445944582f555344000000000000000000000000000000000000000000000000","0x02f901e50183ee43c1b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000008000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400040000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994402a30f83bbfc2203e1fc5d8a9bb41e1b0ddc639e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024df2e4ee100000000000000000000000000000000000000000000000000000000652d2a5f00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183eedc86b9010000000000000000000000000000000000000000000000000000000000000000040000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000f8dbf8d9943ae963e586b6c1d16f371ac0a1260cdaed6a76bde1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024dcbf623800000000000000000000000000000000000000000000000000000000652d2a5f00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183ef753fb9010000000000000000000000000000000000000000000000000000000000000040000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000f8dbf8d994c8f4aeb27fce1f361cda3aadcda992c7ed7b0e74e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000005b67ef00000000000000000000000000000000000000000000000000000000652d2a610000000000000000000000000000000000000000000000000000000000000008444f47452f555344000000000000000000000000000000000000000000000000","0x02f901e50183f00e04b9010000000000000000000000000000000000000000000000000000000004000000000000000000000000001000000000000400000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99460cfba755fac7178e9a8e133699ad2f7dcf6ad9ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000229ca2bd00000000000000000000000000000000000000000000000000000000652d2a620000000000000000000000000000000000000000000000000000000000000008555255532f555344000000000000000000000000000000000000000000000000","0x02f901098083f0647ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183f0fd33b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9945e3b4e52af7f15f4e4e12033d71cfc3afbc7d3c0e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000002c0f30100000000000000000000000000000000000000000000000000000000652d2a6400000000000000000000000000000000000000000000000000000000000000074f4d472f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f195ecb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000f8dbf8d994ffd9e1167e2ad8f323464832ad99a03bda99b7b7e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000150ea000000000000000000000000000000000000000000000000000000000652d2a64000000000000000000000000000000000000000000000000000000000000000847414c412f555344000000000000000000000000000000000000000000000000","0x02f901098083f22563b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183f2be28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9940e324d90e9180df65e63438b2af37458b7b7b500e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000004f47321cd00000000000000000000000000000000000000000000000000000000652d2a690000000000000000000000000000000000000000000000000000000000000007424e422f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f356f9b9010000000000000000000000000000000000000000000000000000000000000000000000000000200000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000f8dbf8d9944a7d0e32e82aea46773c348896761addc51dfb11e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000105dd23061100000000000000000000000000000000000000000000000000000000652d2a6900000000000000000000000000000000000000000000000000000000000000074254432f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f3e4dab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99416324d80bfc68b1fec6c288f0dac640a044d2678e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000018227472200000000000000000000000000000000000000000000000000000000652d2a690000000000000000000000000000000000000000000000000000000000000008414156452f555344000000000000000000000000000000000000000000000000","0x02f901e50183f472c7b9010000000000000000000000000400000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000800040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994e7a43467520e4d12d1f9e94b99d6f041786aadcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000285d0fb605900000000000000000000000000000000000000000000000000000000652d2a6a0000000000000000000000000000000000000000000000000000000000000008574254432f555344000000000000000000000000000000000000000000000000","0x02f901e50183f50b8cb9010000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000000000000000000010000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99407c4eee621098c526403b30bdcb17b3722719dcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024dfc6ecd300000000000000000000000000000000000000000000000000000000652d2a6a00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f59979b9010000000000000000000000000000000000000000000000000000000000000000000000080000000000001000000020000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994b52a8b962ff3d8a6a0937896ff3da3879eac64e3e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000002870fd6b6be00000000000000000000000000000000000000000000000000000000652d2a6a0000000000000000000000000000000000000000000000000000000000000008574254432f555344000000000000000000000000000000000000000000000000","0x02f904240183f78797b9010000000000100000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000800600000000000000000000000000000000000000000001082000000000001001000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000580000000000000000000000200000000001000000000000000000000000000000000000000000000000000000000000000000000000020000000000000004000000000000000000000000020000000000000000000000000000000000000000000000000000000000000020000f90319f901dc949054f0d5f352fafe6ebf0ec14654da0362dc96caf842a0f6a97944f31ea060dfde0566e4167c1a1082551e64b60ecb14d599a9d023d451a00000000000000000000000000000000000000000000000000000000000011eb6b901800000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000006af57e73d328e2a8ec95e01178d1e2a2a387d66a00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000140000000000000000000000012332e3d78c6cc4a1a0c4dae81535bc000065fa80300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c0000000000000000000000000000000000000000000000000000000000000000040001020300000000000000000000000000000000000000000000000000000000f89b949054f0d5f352fafe6ebf0ec14654da0362dc96caf863a00109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271a00000000000000000000000000000000000000000000000000000000000011eb6a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000652d2a70f89b949054f0d5f352fafe6ebf0ec14654da0362dc96caf863a00559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5fa00000000000000000000000000000000000000000000000d51cfff1e9a1af7c00a00000000000000000000000000000000000000000000000000000000000011eb6a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f901e50183f8156cb9010000000000000000000000000000000000000000000000000000004000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000800000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994bbbf9614de2b788a66d970b552a79fae6419abdce1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000001c062a100000000000000000000000000000000000000000000000000000000652d2a6c000000000000000000000000000000000000000000000000000000000000000853414e442f555344000000000000000000000000000000000000000000000000","0x02f901e50183f8ae31b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000f8dbf8d994e5d686595da780e6fbe88c31b77c1225974c89fbe1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024df3d91e000000000000000000000000000000000000000000000000000000000652d2a6d00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f93c06b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200f8dbf8d99439f46d72bb20c7bcb8a2cdf52630fac1496e859ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005f4f04b00000000000000000000000000000000000000000000000000000000652d2a6e0000000000000000000000000000000000000000000000000000000000000008555344432f555344000000000000000000000000000000000000000000000000","0x02f901090183f9a412b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901c80183faf748b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000008000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000080000000000000004000000000800000000000000000000000000000000000000000000000200000000000000000400000000010000000000000000000000000000000000000000000000004800200000000000000000004000000010000000000000000000000000000000f8bef8bc94e5ff3b57695079f808a24256734483cd3889fa9ef884a0a7aaf2512769da4e444e3de247be2564225c2e7a8f74cfe528e46e17d24868e2a08a767b2e89133b95f135e6803d3b75eb52b9636707f38d986de63283fd028beba0000000000000000000000000000000000000000000000000000000000000803ca000000000000000000000000000000000000000000000000000000000003c1c98a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f901090183fb5cb4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fbb880b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fdf200b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fe5538b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183febd24b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf90246018401009066b9010000000000000002000000000000000000000000000000000000800000000000000000000000000000000000000800000000000000000000000000000000000000000100000000000000000008020000000000040000000000000000000000000000000000000008000000000000000000100000000000000000800010800000000000000000000000000000010000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000002000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000040000000004000000000000044f9013af89b9470e53130c4638aa80d5a8abf12983f66e0b1d05ff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003e62eb1d9f503f1db36bcfcabdaa7488718eee09a0000000000000000000000000e8c84a631d71e1bb7083d3a82a3a74870a286b97a0000000000000000000000000000000000000000000000000016345785d8a0000f89b943e62eb1d9f503f1db36bcfcabdaa7488718eee09f863a0cb339b570a7f0b25afa7333371ff11192092a0aeace12b671f4c212f2815c6fea0000000000000000000000000000000000000000000000000000000000000235aa0000000000000000000000000e8c84a631d71e1bb7083d3a82a3a74870a286b97a0191ae0366d4470dec94ac0ad040496453fecc903ffbad13da54e39498f84b0ff","0x02f9010a01840100f0f6b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010158f2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840101d1feb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010239f6b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840102a1f2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f90365018401048fdfb9010000000002000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020000000000000002000000000000000000800000008000000000000040000000000000000002000000000000000000000000000100000000200000000000000200000000010000800000000000000000000000008000000000000000000000000000000000000000004000000000000000000000000800000000000000000000008000000002000000000000002000000000000000000000000000000000000800000000400000000000000000000002000000000000000000000004000000000000000000000000200f90259f89b948f6d296024766b919473f0ef3b1f7addd3f710dff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003dd070f9ee183bd667700d668b35b8932438118ea0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6a000000000000000000000000000000000000000000000000000000000000004d6f89b94d87ba7a50b2e7e660f678a895e4b72e7cb4ccd9cf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6a00000000000000000000000003dd070f9ee183bd667700d668b35b8932438118ea00000000000000000000000000000000000000000000000000000000000550ab9f9011c943dd070f9ee183bd667700d668b35b8932438118ef863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a0000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564a0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6b8a0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2a0000000000000000000000000000000000000000000000000000000000550ab90000000000000000000000000000000000000042fb2bb8fcdc72e6c302336a2800000000000000000000000000000000000000000000000000000000609fce5a000000000000000000000000000000000000000000000000000000000001487c","0x02f9010a01840105994fb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840106126bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf9018601840106f418b9010000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000001000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000010f87bf87994b33a774f60c3eeea880c09bd56f18def648f8fbbe1a0b78ebc573f1f889ca9e1e0fb62c843c836f3d3a2e1f43ef62940e9b894f4ea4cb8400000000000000000000000000000000000000000000000000e507b8392b34d0000000000000000000000000000000000000000000000000000000000652d2a70","0x02f9010a018401075be8b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901c901840108af12b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000100000000000000040000000000000000000000000000000000000000000000020000000000000000000000000000000000080000000100000000000000000000000000000000000000200000000000000000000000000000000000000004000020000800000000000000000000000000000000000000000000000000000000000000000000000000002000400000000000000000000000000000000000040000000000000000000001000000000000000000000000000000000000000000040000f8bef8bc943f97a3e25166de26eef93ad91e382215b21fecf7f884a0a7aaf2512769da4e444e3de247be2564225c2e7a8f74cfe528e46e17d24868e2a048f9d6c5064b083c5c5f17c6f18f8ac529863c506fc2d65b5ebb26571dfa1ec0a00000000000000000000000000000000000000000000000000000000000007097a0000000000000000000000000000000000000000000000000000000000034c740a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f9010a01840109284ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840109a406b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010a1d2eb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010a8536b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010aead2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010b52b2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010bc95ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010c428ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf901a80184010cc606b9010000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000080000000000000020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000040001000000000000000000000000000000000000000002000000008000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000f89df89b94ccb2505976e9d2fd355c73c3f1a004446d1dfedaf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c813edb526830d24a2ce5801d9ef5026a3967529a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000a"]}
diff --git OP/proxyd/tools/mockserver/node2.yml CELO/proxyd/tools/mockserver/node2.yml deleted file mode 100644 index b94ee7af25b6f11593b0929af444db0dec2c3378..0000000000000000000000000000000000000000 --- OP/proxyd/tools/mockserver/node2.yml +++ /dev/null @@ -1,44 +0,0 @@ -- method: eth_getBlockByNumber - block: latest - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash2", - "number": "0x2" - } - } -- method: eth_getBlockByNumber - block: 0x1 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash1", - "number": "0x1" - } - } -- method: eth_getBlockByNumber - block: 0x2 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash2", - "number": "0x2" - } - } -- method: eth_getBlockByNumber - block: 0x3 - response: > - { - "jsonrpc": "2.0", - "id": 67, - "result": { - "hash": "hash3", - "number": "0x3" - } - } \ No newline at end of file
(deleted)
+0
-23
diff --git OP/tsconfig.json CELO/tsconfig.json deleted file mode 100644 index f389a4990f717d16a8571c35e0a4063ad8ab98e9..0000000000000000000000000000000000000000 --- OP/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es2017", - "sourceMap": true, - "esModuleInterop": true, - "composite": true, - "resolveJsonModule": true, - "declaration": true, - "noImplicitAny": false, - "removeComments": true, - "noLib": false, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "typeRoots": [ - "node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "dist" - ] -}
diff --git OP/.circleci/config.yml CELO/.circleci/config.yml index de393bf981880f292eecc77be336a3431175b329..026848b79e0ef7b0e9b86a0949902cc068344a33 100644 --- OP/.circleci/config.yml +++ CELO/.circleci/config.yml @@ -35,51 +35,8 @@ default: false   orbs: go: circleci/go@1.8.0 - gcp-cli: circleci/gcp-cli@3.0.1 - slack: circleci/slack@4.10.1 shellcheck: circleci/shellcheck@3.2.0 commands: - gcp-oidc-authenticate: - description: "Authenticate with GCP using a CircleCI OIDC token." - parameters: - project_id: - type: env_var_name - default: GCP_PROJECT_ID - workload_identity_pool_id: - type: env_var_name - default: GCP_WIP_ID - workload_identity_pool_provider_id: - type: env_var_name - default: GCP_WIP_PROVIDER_ID - service_account_email: - type: env_var_name - default: GCP_SERVICE_ACCOUNT_EMAIL - gcp_cred_config_file_path: - type: string - default: /home/circleci/gcp_cred_config.json - oidc_token_file_path: - type: string - default: /home/circleci/oidc_token.json - steps: - - run: - name: "Create OIDC credential configuration" - command: | - # Store OIDC token in temp file - echo $CIRCLE_OIDC_TOKEN > << parameters.oidc_token_file_path >> - # Create a credential configuration for the generated OIDC ID Token - gcloud iam workload-identity-pools create-cred-config \ - "projects/${<< parameters.project_id >>}/locations/global/workloadIdentityPools/${<< parameters.workload_identity_pool_id >>}/providers/${<< parameters.workload_identity_pool_provider_id >>}"\ - --output-file="<< parameters.gcp_cred_config_file_path >>" \ - --service-account="${<< parameters.service_account_email >>}" \ - --credential-source-file=<< parameters.oidc_token_file_path >> - - run: - name: "Authenticate with GCP using OIDC" - command: | - # Configure gcloud to leverage the generated credential configuration - gcloud auth login --brief --cred-file "<< parameters.gcp_cred_config_file_path >>" - # Configure ADC - echo "export GOOGLE_APPLICATION_CREDENTIALS='<< parameters.gcp_cred_config_file_path >>'" | tee -a "$BASH_ENV" - check-changed: description: "Conditionally halts a step if certain modules change" parameters: @@ -110,12 +67,8 @@ mentions: type: string default: "" steps: - - slack/notify: - channel: << parameters.channel >> - event: fail - template: basic_fail_1 - branch_pattern: develop - mentions: "<< parameters.mentions >>" + - run: + command: "true" # No notifications setup up for celo-org fork   jobs: cannon-go-lint-and-test: @@ -231,6 +184,12 @@ - run: name: Copy Plasma allocs to .devnet-plasma command: cp -r .devnet/ .devnet-plasma-generic/ - run: + name: Generate Celo allocs + command: DEVNET_L2OO="true" DEVNET_CELO="true" make devnet-allocs + - run: + name: Copy Celo allocs to .devnet-celo + command: cp -r .devnet/ .devnet-celo/ + - run: name: Generate default allocs command: make devnet-allocs - persist_to_workspace: @@ -247,6 +206,12 @@ - ".devnet/allocs-l2-delta.json" - ".devnet/allocs-l2-ecotone.json" - ".devnet/allocs-l2-fjord.json" - ".devnet/addresses.json" + - ".devnet-celo/addresses.json" + - ".devnet-celo/allocs-l1.json" + - ".devnet-celo/allocs-l2-delta.json" + - ".devnet-celo/allocs-l2-ecotone.json" + - ".devnet-celo/allocs-l2-fjord.json" + - ".devnet-celo/addresses.json" - ".devnet-l2oo/allocs-l1.json" - ".devnet-l2oo/addresses.json" - ".devnet-l2oo/allocs-l2-delta.json" @@ -308,24 +273,13 @@ default: medium machine: image: <<pipeline.parameters.base_image>> resource_class: "<<parameters.resource_class>>" - docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages + docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages steps: - checkout - attach_workspace: at: /tmp/docker_images - run: command: mkdir -p /tmp/docker_images - - when: - condition: "<<parameters.release>>" - steps: - - gcp-cli/install - - when: - condition: - or: - - "<<parameters.publish>>" - - "<<parameters.release>>" - steps: - - gcp-oidc-authenticate - run: name: Build command: | @@ -403,10 +357,6 @@ <<parameters.docker_name>>   no_output_timeout: 45m - when: - condition: "<<parameters.publish>>" - steps: - - notify-failures-on-develop - - when: condition: "<<parameters.save_image_tag>>" steps: - run: @@ -416,87 +366,8 @@ IMAGE_NAME="<<parameters.registry>>/<<parameters.repo>>/<<parameters.docker_name>>:<<parameters.save_image_tag>>" docker save -o /tmp/docker_images/<<parameters.docker_name>>.tar $IMAGE_NAME - persist_to_workspace: root: /tmp/docker_images - paths: # only write the one file, to avoid concurrent workspace-file additions + paths: # only write the one file, to avoid concurrent workspace-file additions - "<<parameters.docker_name>>.tar" - - when: - condition: "<<parameters.release>>" - steps: - - run: - name: Tag - command: | - ./ops/scripts/ci-docker-tag-op-stack-release.sh <<parameters.registry>>/<<parameters.repo>> $CIRCLE_TAG $CIRCLE_SHA1 - - when: - condition: - and: - - or: - - "<<parameters.publish>>" - - "<<parameters.release>>" - - equal: [develop, << pipeline.git.branch >>] - steps: - - gcp-oidc-authenticate: - service_account_email: GCP_SERVICE_ATTESTOR_ACCOUNT_EMAIL - - run: - name: Sign - command: | - git clone https://github.com/ethereum-optimism/binary_signer - cd binary_signer/signer - git checkout tags/v1.0.3 - - IMAGE_PATH="<<parameters.registry>>/<<parameters.repo>>/<<parameters.docker_name>>:<<pipeline.git.revision>>" - echo $IMAGE_PATH - pip3 install -r requirements.txt - - python3 ./sign_image.py --command="sign"\ - --attestor-project-name="$ATTESTOR_PROJECT_NAME"\ - --attestor-name="$ATTESTOR_NAME"\ - --image-path="$IMAGE_PATH"\ - --signer-logging-level="INFO"\ - --attestor-key-id="//cloudkms.googleapis.com/v1/projects/$ATTESTOR_PROJECT_NAME/locations/global/keyRings/$ATTESTOR_NAME-key-ring/cryptoKeys/$ATTESTOR_NAME-key/cryptoKeyVersions/1" - - # Verify newly published images (built on AMD machine) will run on ARM - check-cross-platform: - docker: - - image: cimg/base:current - resource_class: arm.medium - parameters: - registry: - description: Docker registry - type: string - default: "us-docker.pkg.dev" - repo: - description: Docker repo - type: string - default: "oplabs-tools-artifacts/images" - op_component: - description: "Name of op-stack component (e.g. op-node)" - type: string - default: "" - docker_tag: - description: "Tag of docker image" - type: string - default: "<<pipeline.git.revision>>" - steps: - - setup_remote_docker - - run: - name: "Verify Image Platform" - command: | - image_name="<<parameters.registry>>/<<parameters.repo>>/<<parameters.op_component>>:<<parameters.docker_tag>>" - echo "Retrieving Docker image manifest: $image_name" - MANIFEST=$(docker manifest inspect $image_name) - - echo "Verifying 'linux/arm64' is supported..." - SUPPORTED_PLATFORM=$(echo "$MANIFEST" | jq -r '.manifests[] | select(.platform.architecture == "arm64" and .platform.os == "linux")') - echo $SUPPORT_PLATFORM - if [ -z "$SUPPORTED_PLATFORM" ]; then - echo "Platform 'linux/arm64' not supported by this image" - exit 1 - fi - - run: - name: "Pull and run docker image" - command: | - image_name="<<parameters.registry>>/<<parameters.repo>>/<<parameters.op_component>>:<<parameters.docker_tag>>" - docker pull $image_name || exit 1 - docker run $image_name <<parameters.op_component>> --version || exit 1   contracts-bedrock-coverage: docker: @@ -595,7 +466,7 @@ environment: FOUNDRY_PROFILE: ci working_directory: packages/contracts-bedrock - run: - # Semver lock must come second because one of the later steps may modify the cache & force a contracts rebuild. + # Semver lock must come second because one of the later steps may modify the cache & force a contracts rebuild. name: semver lock command: | pnpm semver-lock @@ -854,8 +725,8 @@ paths: - "/root/.cache/go-build" # TODO(CLI-148): Fix codecov #- run: - #name: upload coverage - #command: codecov --verbose --clean --flags bedrock-go-tests + #name: upload coverage + #command: codecov --verbose --clean --flags bedrock-go-tests - store_test_results: path: /tmp/test-results - store_artifacts: @@ -866,7 +737,7 @@ go-e2e-test: parameters: variant: type: string - default: '' + default: "" module: description: Go Module Name type: string @@ -886,8 +757,8 @@ description: Slack user or group to mention when notifying of failures type: string default: "" environment: - DEVNET_L2OO: 'false' - OP_E2E_USE_L2OO: 'false' + DEVNET_L2OO: "false" + OP_E2E_USE_L2OO: "false" docker: - image: <<pipeline.parameters.ci_builder_image>> resource_class: xlarge @@ -896,7 +767,7 @@ steps: - checkout - when: condition: - equal: ['-l2oo', <<parameters.variant>>] + equal: ["-l2oo", <<parameters.variant>>] steps: - run: name: Set DEVNET_L2OO = true @@ -906,7 +777,7 @@ name: Set OP_E2E_USE_L2OO = true command: echo 'export OP_E2E_USE_L2OO=true' >> $BASH_ENV - when: condition: - equal: ['-plasma', <<parameters.variant>>] + equal: ["-plasma", <<parameters.variant>>] steps: - run: name: Set OP_E2E_USE_PLASMA = true @@ -1006,7 +877,7 @@ path: /testlogs when: always - when: condition: - equal: [ true, <<parameters.build>> ] + equal: [true, <<parameters.build>>] steps: - run: name: Build @@ -1071,6 +942,8 @@ elif [[ "<<parameters.version>>" == "1.0.0" ]]; then echo 'export EXPECTED_PRESTATE_HASH="0x037ef3c1a487960b0e633d3e513df020c43432769f41a634d18a9595cbf53c55"' >> $BASH_ENV elif [[ "<<parameters.version>>" == "1.1.0" ]]; then echo 'export EXPECTED_PRESTATE_HASH="0x03e69d3de5155f4a80da99dd534561cbddd4f9dd56c9ecc704d6886625711d2b"' >> $BASH_ENV + elif [[ "<<parameters.version>>" == "1.2.0" ]]; then + echo 'export EXPECTED_PRESTATE_HASH="0x03617abec0b255dc7fc7a0513a2c2220140a1dcd7a1c8eca567659bd67e05cea"' >> $BASH_ENV else echo "Unknown prestate version <<parameters.version>>" exit 1 @@ -1091,7 +964,6 @@ exit 1 fi - notify-failures-on-develop: mentions: "@proofs-squad" -   devnet-allocs: docker: @@ -1129,29 +1001,32 @@ variant: type: string environment: DOCKER_BUILDKIT: 1 - DEVNET_NO_BUILD: 'true' + DEVNET_NO_BUILD: "true" # Default value; Can be overridden. - DEVNET_L2OO: 'false' - DEVNET_PLASMA: 'false' + DEVNET_L2OO: "false" + DEVNET_PLASMA: "false" steps: - checkout - when: condition: - equal: ['l2oo', <<parameters.variant>>] + equal: ["plasma", <<parameters.variant>>] steps: - run: - name: Set DEVNET_L2OO = true - command: echo 'export DEVNET_L2OO=true' >> $BASH_ENV + name: Set DEVNET_PLASMA = true + command: echo 'export DEVNET_PLASMA=true' >> $BASH_ENV - when: condition: - equal: ['plasma', <<parameters.variant>>] + equal: ["celo", <<parameters.variant>>] steps: - run: - name: Set DEVNET_PLASMA = true - command: echo 'export DEVNET_PLASMA=true' >> $BASH_ENV + name: Set DEVNET_CELO = true + command: echo 'export DEVNET_CELO=true' >> $BASH_ENV + - run: + name: Set DEVNET_L2OO = true + command: echo 'export DEVNET_L2OO=true' >> $BASH_ENV - when: condition: - equal: ['plasma-generic', <<parameters.variant>>] + equal: ["plasma-generic", <<parameters.variant>>] steps: - run: name: Set DEVNET_PLASMA = true @@ -1209,7 +1084,7 @@ at: "." - when: condition: not: - equal: ['default', <<parameters.variant>>] + equal: ["default", <<parameters.variant>>] steps: - run: name: Use non-default devnet allocs @@ -1239,9 +1114,35 @@ name: Bring up the stack command: | # Specify like this to avoid a forced rebuild of the contracts + devnet L1 PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=. - - run: - name: Test the stack - command: make devnet-test + - when: + condition: + not: + equal: ["celo", <<parameters.variant>>] + steps: + - run: + name: Test the stack + command: make devnet-test + - when: + condition: + equal: ["celo", <<parameters.variant>>] + steps: + - restore_cache: + key: v1-celo-deps-{{ checksum "op-e2e/celo/package-lock.json" }} + # install dependencies + - run: + working_directory: op-e2e/celo + name: install dependencies + command: npm install + # save any changes to the cache + - save_cache: + key: v1-celo-deps-{{ checksum "op-e2e/celo/package-lock.json" }} + paths: + - op-e2e/celo/node_modules + - run: + working_directory: op-e2e/celo + name: Run Celo e2e tests + command: | + SPAWN_DEVNET=false ./run_all_tests.sh - run: name: Dump op-node logs command: | @@ -1317,7 +1218,7 @@ steps: - checkout - unless: condition: - equal: [ "develop", << pipeline.git.branch >> ] + equal: ["develop", << pipeline.git.branch >>] steps: - run: # Scan changed files in PRs, block on new issues only (existing issues ignored) @@ -1411,10 +1312,10 @@ -run TestRethDB -tags rethdb -v working_directory: op-service/sources # TODO(CLI-148): Fix codecov #- run: - #name: upload coverage - #command: codecov --verbose --clean --flags bedrock-rethdb-go-tests + #name: upload coverage + #command: codecov --verbose --clean --flags bedrock-rethdb-go-tests   - bedrock-go-tests: # just a helper, that depends on all the actual test jobs + bedrock-go-tests: # just a helper, that depends on all the actual test jobs docker: # Use a smaller base image to avoid pulling the huge ci-builder # image which is not needed for this job and sometimes misses @@ -1424,35 +1325,6 @@ resource_class: medium steps: - run: echo Done   - fpp-verify: - docker: - - image: cimg/go:1.21 - steps: - - checkout - - run: - name: verify-sepolia - command: | - make verify-sepolia - working_directory: op-program - - notify-failures-on-develop: - mentions: "@proofs-squad" - - op-program-compat: - docker: - - image: <<pipeline.parameters.ci_builder_image>> - steps: - - checkout - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - - restore_cache: - key: golang-build-cache - - run: - name: compat-sepolia - command: | - make verify-compat - working_directory: op-program - check-generated-mocks-op-node: docker: - image: <<pipeline.parameters.ci_builder_image>> @@ -1528,11 +1400,11 @@ when: and: - or: # Trigger on new commits - - equal: [ webhook, << pipeline.trigger_source >> ] - # Trigger on manual triggers if explicitly requested - - equal: [ true, << pipeline.parameters.main_dispatch >> ] + - equal: [webhook, << pipeline.trigger_source >>] + # Trigger on manual triggers if explicitly requested + - equal: [true, << pipeline.parameters.main_dispatch >>] - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] + equal: [scheduled_pipeline, << pipeline.trigger_source >>] jobs: - pnpm-monorepo: name: pnpm-monorepo @@ -1560,10 +1432,6 @@ package_name: sdk dependencies: "contracts-bedrock" requires: - pnpm-monorepo - - go-lint-test-build: - name: proxyd-tests - binary_name: proxyd - working_directory: proxyd - semgrep-scan - go-mod-download - fuzz-golang: @@ -1601,7 +1469,7 @@ requires: ["go-mod-download", "pnpm-monorepo"] - go-test: name: op-heartbeat-tests module: op-heartbeat - requires: [ "go-mod-download" ] + requires: ["go-mod-download"] - go-test: name: op-batcher-tests module: op-batcher @@ -1637,6 +1505,10 @@ requires: ["go-mod-download"] - go-test: name: op-service-tests module: op-service + requires: ["go-mod-download"] + - go-test: + name: op-supervisor-tests + module: op-supervisor requires: ["go-mod-download"] - go-e2e-test: name: op-e2e-HTTP-tests<< matrix.variant >> @@ -1671,9 +1543,9 @@ - cannon-prestate - op-service-rethdb-tests: requires: - go-mod-download - - op-program-compat: + - cannon-prestate: requires: - - op-program-tests + - go-mod-download - bedrock-go-tests: requires: - go-mod-download @@ -1691,12 +1563,38 @@ - op-challenger-tests - op-dispute-mon-tests - op-conductor-tests - op-program-tests - - op-program-compat - op-service-tests + - op-supervisor-tests - op-service-rethdb-tests - op-e2e-HTTP-tests - op-e2e-fault-proof-tests - op-e2e-action-tests + - op-e2e-action-tests-plasma + - check-generated-mocks-op-node + - check-generated-mocks-op-service + - cannon-go-lint-and-test: + requires: + - pnpm-monorepo + - cannon-build-test-vectors + - shellcheck/check: + name: shell-check + # We don't need the `exclude` key as the orb detects the `.shellcheckrc` + dir: . + ignore-dirs: ./packages/contracts-bedrock/lib + merged: + when: + and: + - or: + # Trigger after merged into celoXX branch + - matches: { pattern: '^celo\d+$', value: << pipeline.git.branch >> } + # Trigger on manual triggers if explicitly requested + - equal: [true, << pipeline.parameters.main_dispatch >>] + - not: + equal: [scheduled_pipeline, << pipeline.trigger_source >>] + jobs: + - pnpm-monorepo: + name: pnpm-monorepo + - go-mod-download - docker-build: name: op-node-docker-build docker_name: op-node @@ -1742,13 +1640,18 @@ name: da-server-docker-build docker_name: da-server docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> save_image_tag: <<pipeline.git.revision>> # for devnet later + - docker-build: + name: op-supervisor-docker-build + docker_name: op-supervisor + docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> + # op-supervisor is not (yet) part of the devnet, we don't save it - cannon-prestate: requires: - go-mod-download - devnet: matrix: parameters: - variant: ["default", "l2oo", "plasma", "plasma-generic"] + variant: ["celo"] requires: - pnpm-monorepo - op-batcher-docker-build @@ -1757,437 +1660,3 @@ - op-node-docker-build - op-challenger-docker-build - da-server-docker-build - cannon-prestate - - check-generated-mocks-op-node - - check-generated-mocks-op-service - - cannon-go-lint-and-test: - requires: - - pnpm-monorepo - - cannon-build-test-vectors - - shellcheck/check: - name: shell-check - # We don't need the `exclude` key as the orb detects the `.shellcheckrc` - dir: . - ignore-dirs: - ./packages/contracts-bedrock/lib - - release: - when: - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - - hold: - type: approval - filters: - tags: - only: /^(da-server|proxyd|chain-mon|ci-builder(-rust)?|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)\/v.*/ - branches: - ignore: /.*/ - - docker-build: - name: op-heartbeat-release - filters: - tags: - only: /^op-heartbeat\/v.*/ - branches: - ignore: /.*/ - docker_name: op-heartbeat - docker_tags: <<pipeline.git.revision>> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-heartbeat-cross-platform - op_component: op-heartbeat - requires: - - op-heartbeat-release - - docker-build: - name: op-node-docker-release - filters: - tags: - only: /^op-node\/v.*/ - branches: - ignore: /.*/ - docker_name: op-node - docker_tags: <<pipeline.git.revision>> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-node-cross-platform - op_component: op-node - requires: - - op-node-docker-release - - docker-build: - name: op-batcher-docker-release - filters: - tags: - only: /^op-batcher\/v.*/ - branches: - ignore: /.*/ - docker_name: op-batcher - docker_tags: <<pipeline.git.revision>> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-batcher-cross-platform - op_component: op-batcher - requires: - - op-batcher-docker-release - - docker-build: - name: op-proposer-docker-release - filters: - tags: - only: /^op-proposer\/v.*/ - branches: - ignore: /.*/ - docker_name: op-proposer - docker_tags: <<pipeline.git.revision>> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-proposer-cross-platform - op_component: op-proposer - requires: - - op-proposer-docker-release - - docker-build: - name: op-challenger-docker-release - filters: - tags: - only: /^op-challenger\/v.*/ - branches: - ignore: /.*/ - docker_name: op-challenger - docker_tags: <<pipeline.git.revision>> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-challenger-cross-platform - op_component: op-challenger - requires: - - op-challenger-docker-release - - docker-build: - name: op-dispute-mon-docker-release - filters: - tags: - only: /^op-dispute-mon\/v.*/ - branches: - ignore: /.*/ - docker_name: op-dispute-mon - docker_tags: <<pipeline.git.revision>> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-dispute-mon-cross-platform - op_component: op-dispute-mon - requires: - - op-dispute-mon-docker-release - - docker-build: - name: op-conductor-docker-release - filters: - tags: - only: /^op-conductor\/v.*/ - branches: - ignore: /.*/ - docker_name: op-conductor - docker_tags: <<pipeline.git.revision>> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-conductor-cross-platform - op_component: op-conductor - requires: - - op-conductor-docker-release - - docker-build: - name: da-server-docker-release - filters: - tags: - only: /^da-server\/v.*/ - branches: - ignore: /.*/ - docker_name: da-server - docker_tags: <<pipeline.git.revision>> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: da-server-cross-platform - op_component: da-server - requires: - - da-server-docker-release - - docker-build: - name: op-ufm-docker-release - filters: - tags: - only: /^op-ufm\/v.*/ - branches: - ignore: /.*/ - docker_name: op-ufm - docker_tags: <<pipeline.git.revision>> - publish: true - release: true - context: - - oplabs-gcr-release - requires: - - hold - - docker-build: - name: proxyd-docker-release - filters: - tags: - only: /^proxyd\/v.*/ - branches: - ignore: /.*/ - docker_name: proxyd - docker_tags: <<pipeline.git.revision>> - publish: true - release: true - context: - - oplabs-gcr-release - requires: - - hold - - docker-build: - name: chain-mon-docker-release - filters: - tags: - only: /^chain-mon\/v.*/ - branches: - ignore: /.*/ - docker_name: chain-mon - docker_tags: <<pipeline.git.revision>>,latest - publish: true - release: true - resource_class: xlarge - context: - - oplabs-gcr-release - requires: - - hold - - docker-build: - name: ci-builder-docker-release - filters: - tags: - only: /^ci-builder\/v.*/ - branches: - ignore: /.*/ - docker_name: ci-builder - docker_tags: <<pipeline.git.revision>>,latest - publish: true - release: true - resource_class: xlarge - context: - - oplabs-gcr - requires: - - hold - - docker-build: - name: ci-builder-rust-docker-release - filters: - tags: - only: /^ci-builder-rust\/v.*/ - branches: - ignore: /.*/ - docker_name: ci-builder-rust - docker_tags: <<pipeline.git.revision>>,latest - publish: true - release: true - resource_class: xlarge - context: - - oplabs-gcr - requires: - - hold - - scheduled-todo-issues: - when: - equal: [ build_four_hours, <<pipeline.schedule.name>> ] - jobs: - - todo-issues: - name: todo-issue-checks - context: - - slack - - scheduled-fpp: - when: - equal: [ build_four_hours, <<pipeline.schedule.name>> ] - jobs: - - fpp-verify: - context: - - slack - - oplabs-fpp-nodes - - develop-fault-proofs: - when: - and: - - or: - - equal: [ "develop", <<pipeline.git.branch>> ] - - equal: [ true, <<pipeline.parameters.fault_proofs_dispatch>> ] - - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - - go-mod-download - - cannon-prestate: - requires: - - go-mod-download - - pnpm-monorepo: - name: pnpm-monorepo - requires: - - go-mod-download - - go-e2e-test: - name: op-e2e-cannon-tests - module: op-e2e - target: test-cannon - parallelism: 4 - notify: true - mentions: "@proofs-squad" - requires: - - pnpm-monorepo - - cannon-prestate - context: - - slack - - develop-kontrol-tests: - when: - and: - - or: - - equal: [ "develop", <<pipeline.git.branch>> ] - - equal: [ true, <<pipeline.parameters.kontrol_dispatch>> ] - - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - - kontrol-tests: - context: - - slack - - scheduled-docker-publish: - when: - equal: [ build_hourly, <<pipeline.schedule.name>> ] - jobs: - - docker-build: - name: op-node-docker-publish - docker_name: op-node - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-batcher-docker-publish - docker_name: op-batcher - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-program-docker-publish - docker_name: op-program - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-proposer-docker-publish - docker_name: op-proposer - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-challenger-docker-publish - docker_name: op-challenger - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-dispute-mon-docker-publish - docker_name: op-dispute-mon - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-conductor-docker-publish - docker_name: op-conductor - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-heartbeat-docker-publish - docker_name: op-heartbeat - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: chain-mon-docker-publish - docker_name: chain-mon - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - resource_class: xlarge - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: contracts-bedrock-docker-publish - docker_name: contracts-bedrock - docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> - resource_class: xlarge - requires: [ 'chain-mon-docker-publish' ] # use the cached base image - publish: true - context: - - oplabs-gcr - - slack - - scheduled-preimage-reproducibility: - when: - or: - - equal: [build_daily, <<pipeline.schedule.name>> ] - # Trigger on manual triggers if explicitly requested - - equal: [ true, << pipeline.parameters.reproducibility_dispatch >> ] - jobs: - - preimage-reproducibility: - matrix: - parameters: - version: ["0.1.0", "0.2.0", "0.3.0", "1.0.0", "1.1.0"] - context: - slack
diff --git OP/.github/dependabot.yml CELO/.github/dependabot.yml index e2c739a570d569656a412cf955ec8d54757abaeb..ce096cfc17fcb9a16132e9d30ba879bf07b29d69 100644 --- OP/.github/dependabot.yml +++ CELO/.github/dependabot.yml @@ -7,7 +7,7 @@ interval: "daily" day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 commit-message: prefix: "dependabot(docker): " labels: @@ -20,7 +20,7 @@ interval: "weekly" day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 commit-message: prefix: "dependabot(actions): " labels: @@ -33,7 +33,7 @@ interval: "weekly" day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 versioning-strategy: "auto" commit-message: prefix: "dependabot(npm): " @@ -47,7 +47,7 @@ interval: "daily" day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 commit-message: prefix: "dependabot(gomod): " labels:
diff --git OP/.github/mergify.yml CELO/.github/mergify.yml index 7156137c40079e2a7f36fb73f27b7713e9b54e90..2b8627818b9586d13ce681fe40b5e166182f02af 100644 --- OP/.github/mergify.yml +++ CELO/.github/mergify.yml @@ -228,14 +228,6 @@ - A-pkg-sdk request_reviews: users: - roninjin10 - - name: Add A-proxyd label - conditions: - - 'files~=^proxyd/' - - '#label<5' - actions: - label: - add: - - A-proxyd - name: Add M-docs label conditions: - 'files~=^(docs|specs)\/'
diff --git OP/.github/workflows/contracts-celo.yaml CELO/.github/workflows/contracts-celo.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4db01c9406a1dafee59b318a9b26a8b1e99b86e6 --- /dev/null +++ CELO/.github/workflows/contracts-celo.yaml @@ -0,0 +1,99 @@ +name: Alfajores-Holesky Deploy Celo4 L1 Contracts +on: + workflow_dispatch: + inputs: + deploy_contracts: + required: false + type: boolean + default: true + contracts_tag: + required: false + type: string + default: 'celo4' + deployment_context: + required: false + type: string + default: 'test-celo4' + l2_chain_id: + required: false + default: '42069' + +jobs: + deploy-contracts: + runs-on: ubuntu-latest + permissions: # Must change the job token permissions to use Akeyless JWT auth + id-token: write + contents: read + if: ${{ ! startsWith(github.triggering_actor, 'akeyless') }} + env: + DEPLOY_CONTRACTS: ${{ github.event_name == 'push' && 'true' || inputs.deploy_contracts }} + CONTRACTS_TAG: ${{ github.event_name == 'push' && 'op-contracts/v1.3.0' || inputs.contracts_tag }} + DEPLOYMENT_CONTEXT: ${{ github.event_name == 'push' && 'test' || inputs.deployment_context }} + L2_CHAIN_ID: ${{ github.event_name == 'push' && '42069' || inputs.l2_chain_id }} + L1_CHAIN_ID: '17000' # Holesky + L1_RPC_URL: 'https://ethereum-holesky-rpc.publicnode.com' + GS_ADMIN_ADDRESS: '0xb2397dF29AFB4B4661559436180019bEb7912985' + GS_BATCHER_ADDRESS: '0x7fDBe8F4D22ab511340667d7Ce5675568d09eBB4' + GS_PROPOSER_ADDRESS: '0xdCf30236Fa0aBE2ca0BEc2eE0a2F40b16A144DB3' + GS_SEQUENCER_ADDRESS: '0x3e2Df8efB6fA1d6E6021572a99BB67BA9ab2C59D' + steps: + + - name: "Get GitHub Token from Akeyless" + id: get_auth_token + uses: + docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + dynamic-secrets: '{"/dynamic-secrets/keys/github/optimism/contents=write,pull_requests=write":"PAT"}' + + # "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/HOLESKY_QUICKNODE_URL":"L1_RPC_URL", + - name: Akeyless get secrets + uses: docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + static-secrets: '{ + "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/GS_ADMIN_PRIVATE_KEY":"GS_ADMIN_PRIVATE_KEY" + }' + + - name: "Checkout" + uses: actions/checkout@v4 + with: + token: ${{ env.PAT }} + submodules: recursive + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Generate config JSON + run: | + cd packages/contracts-bedrock + ./scripts/getting-started/config-vars-celo.sh + + - name: Deploy L1 contracts + if: ${{ env.DEPLOY_CONTRACTS != 'false' }} + run: | + export IMPL_SALT=$(openssl rand -hex 32) + cd packages/contracts-bedrock + echo "Broadcasting ..." + forge script scripts/Deploy.s.sol:Deploy --private-key $GS_ADMIN_PRIVATE_KEY --broadcast --rpc-url $L1_RPC_URL --legacy + + - name: Generate genesis files + run: | + mkdir -p l2-config-files/$DEPLOYMENT_CONTEXT + cd op-node + go run cmd/main.go genesis l2 \ + --deploy-config ../packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json \ + --l1-deployments ../packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy \ + --outfile.l2 ../l2-config-files/$DEPLOYMENT_CONTEXT/genesis-$(date +%s).json \ + --outfile.rollup ../l2-config-files/$DEPLOYMENT_CONTEXT/rollup-$(date +%s).json \ + --l1-rpc $L1_RPC_URL + + - name: "Commit genesis files" + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: '[Automatic] - Commit genesis files' + branch: alvarof2/contracts + file_pattern: 'l2-config-files packages/contracts-bedrock/**'
diff --git OP/.github/workflows/contracts-op-stack.yaml CELO/.github/workflows/contracts-op-stack.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7c7efb2409c08e44cbf6955d4e8b9fe07398bdef --- /dev/null +++ CELO/.github/workflows/contracts-op-stack.yaml @@ -0,0 +1,117 @@ +name: Alfajores-Holesky Deploy OP-Stack L1 Contracts +on: + workflow_dispatch: + inputs: + deploy_contracts: + required: false + type: boolean + default: true + contracts_tag: + required: false + type: string + default: 'op-contracts/v1.3.0' + deployment_context: + required: false + type: string + default: 'test-alvaro' + l2_chain_id: + required: false + default: '42069' + +jobs: + deploy-contracts: + runs-on: ubuntu-latest + permissions: # Must change the job token permissions to use Akeyless JWT auth + id-token: write + contents: read + if: ${{ ! startsWith(github.triggering_actor, 'akeyless') }} + env: + DEPLOY_CONTRACTS: ${{ github.event_name == 'push' && 'true' || inputs.deploy_contracts }} + CONTRACTS_TAG: ${{ github.event_name == 'push' && 'op-contracts/v1.3.0' || inputs.contracts_tag }} + DEPLOYMENT_CONTEXT: ${{ github.event_name == 'push' && 'test' || inputs.deployment_context }} + L2_CHAIN_ID: ${{ github.event_name == 'push' && '42069' || inputs.l2_chain_id }} + L1_CHAIN_ID: '17000' # Holesky + L1_RPC_URL: 'https://ethereum-holesky-rpc.publicnode.com' + GS_ADMIN_ADDRESS: '0xb2397dF29AFB4B4661559436180019bEb7912985' + GS_BATCHER_ADDRESS: '0x7fDBe8F4D22ab511340667d7Ce5675568d09eBB4' + GS_PROPOSER_ADDRESS: '0xdCf30236Fa0aBE2ca0BEc2eE0a2F40b16A144DB3' + GS_SEQUENCER_ADDRESS: '0x3e2Df8efB6fA1d6E6021572a99BB67BA9ab2C59D' + steps: + + - name: "Get GitHub Token from Akeyless" + id: get_auth_token + uses: + docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + dynamic-secrets: '{"/dynamic-secrets/keys/github/optimism/contents=write,pull_requests=write":"PAT"}' + + # "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/HOLESKY_QUICKNODE_URL":"L1_RPC_URL", + - name: Akeyless get secrets + uses: docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + static-secrets: '{ + "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/GS_ADMIN_PRIVATE_KEY":"GS_ADMIN_PRIVATE_KEY" + }' + + - name: "Checkout" + uses: actions/checkout@v4 + with: + token: ${{ env.PAT }} + submodules: recursive + fetch-depth: 0 + + - name: "Checkout OP Repo" + uses: actions/checkout@v4 + with: + repository: 'ethereum-optimism/optimism' + ref: '${{ env.CONTRACTS_TAG }}' + path: ethereum-optimism + submodules: recursive + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Generate config JSON + run: | + cd packages/contracts-bedrock + ./scripts/getting-started/config-vars-op-stack.sh + cp deploy-config/$DEPLOYMENT_CONTEXT.json /home/runner/work/optimism/optimism/ethereum-optimism/packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json + + - name: Deploy L1 contracts + if: ${{ env.DEPLOY_CONTRACTS != 'false' }} + run: | + export IMPL_SALT=$(openssl rand -hex 32) + cd ethereum-optimism/packages/contracts-bedrock + echo "Broadcasting ..." + forge script scripts/Deploy.s.sol:Deploy --private-key $GS_ADMIN_PRIVATE_KEY --broadcast --rpc-url $L1_RPC_URL --legacy + mkdir -p /home/runner/work/optimism/optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT + cp deployments/$DEPLOYMENT_CONTEXT/.deploy /home/runner/work/optimism/optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy + + - name: Copy old .deploy file if contracts not deployed + if: ${{ env.DEPLOY_CONTRACTS == 'false' }} + run: | + mkdir -p ethereum-optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT + cp packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy ethereum-optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy + + - name: Generate genesis files + run: | + mkdir -p l2-config-files/$DEPLOYMENT_CONTEXT + cd ethereum-optimism/op-node + go run cmd/main.go genesis l2 \ + --deploy-config ../packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json \ + --l1-deployments ../packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy \ + --outfile.l2 ../../l2-config-files/$DEPLOYMENT_CONTEXT/genesis-$(date +%s).json \ + --outfile.rollup ../../l2-config-files/$DEPLOYMENT_CONTEXT/rollup-$(date +%s).json \ + --l1-rpc $L1_RPC_URL + + - name: "Commit genesis files" + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: '[Automatic] - Commit genesis files' + branch: alvarof2/contracts + file_pattern: 'l2-config-files packages/contracts-bedrock/**'
diff --git OP/.github/workflows/docker-build-scan.yaml CELO/.github/workflows/docker-build-scan.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0fa8a1449c70c03d6b709184de692a4786336f5c --- /dev/null +++ CELO/.github/workflows/docker-build-scan.yaml @@ -0,0 +1,102 @@ +name: Docker Build Scan +on: + pull_request: + branches: + - 'master' + - 'celo*' + push: + branches: + - 'master' + - 'celo*' + workflow_dispatch: + +jobs: + detect-files-changed: + runs-on: ubuntu-latest + outputs: + files-changed: ${{ steps.detect-files-changed.outputs.all_changed_files }} + steps: + - uses: actions/checkout@v4 + - name: Detect files changed + id: detect-files-changed + uses: tj-actions/changed-files@v44 + with: + separator: ',' + + build-cel2-migration-tool: + runs-on: ubuntu-latest + env: + GIT_COMMIT: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + needs: detect-files-changed + if: | + contains(needs.detect-files-changed.outputs.files-changed, 'go.sum') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-chain-ops/cmd/celo-migrate') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-chain-ops/Dockerfile') || + contains(needs.detect-files-changed.outputs.files-changed, '.github/workflows/docker-build-scan.yaml') || + github.event_name == 'workflow_dispatch' || + true + permissions: + contents: read + id-token: write + security-events: write + steps: + - uses: actions/checkout@v4 + - name: Login at GCP Artifact Registry + uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@v2.0 + with: + workload-id-provider: 'projects/1094498259535/locations/global/workloadIdentityPools/gh-optimism/providers/github-by-repos' + service-account: 'celo-optimism-gh@devopsre.iam.gserviceaccount.com' + docker-gcp-registries: us-west1-docker.pkg.dev + - name: Build and push container + uses: celo-org/reusable-workflows/.github/actions/build-container@v2.0 + with: + platforms: linux/amd64 + registry: us-west1-docker.pkg.dev/devopsre/dev-images/cel2-migration-tool + tags: ${{ env.GIT_COMMIT }} + context: ./ + dockerfile: ./op-chain-ops/Dockerfile + push: true + trivy: false + + # Build op-node op-batcher op-proposer using docker-bake + build-op-stack: + runs-on: ubuntu-latest + needs: detect-files-changed + if: | + contains(needs.detect-files-changed.outputs.files-changed, 'go.sum') || + contains(needs.detect-files-changed.outputs.files-changed, 'ops/docker') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-node/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-batcher/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-proposer/') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-service/') || + contains(needs.detect-files-changed.outputs.files-changed, '.github/workflows/docker-build-scan.yaml') || + github.event_name == 'workflow_dispatch' || + true + permissions: + contents: read + id-token: write + security-events: write + env: + GIT_COMMIT: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + GIT_DATE: ${{ github.event.head_commit.timestamp }} + IMAGE_TAGS: ${{ (github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/celo')) && 'latest,' || '') }}${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + REGISTRY: us-west1-docker.pkg.dev + REPOSITORY: blockchaintestsglobaltestnet/dev-images + steps: + - uses: actions/checkout@v4 + - name: Login at GCP Artifact Registry + uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@v2.0 + with: + workload-id-provider: 'projects/1094498259535/locations/global/workloadIdentityPools/gh-optimism/providers/github-by-repos' + service-account: 'celo-optimism-gh@devopsre.iam.gserviceaccount.com' + docker-gcp-registries: us-west1-docker.pkg.dev + # We need a custom steps as it's using docker bake + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push + uses: docker/bake-action@v5 + with: + push: true + source: . + files: docker-bake.hcl + targets: op-node,op-batcher,op-proposer
diff --git OP/.github/workflows/docker-op-ufm-build-push.yaml CELO/.github/workflows/docker-op-ufm-build-push.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e4a0ab33b1033742801b286de8003032909575ef --- /dev/null +++ CELO/.github/workflows/docker-op-ufm-build-push.yaml @@ -0,0 +1,41 @@ +--- +name: Build op-ufm container and push to cLabs registry +on: + push: + branches: + - cel4 + paths: + # Run if any of the following files are changed + - 'op-ufm/**' + workflow_dispatch: + +jobs: + build: + runs-on: ['self-hosted', 'org', '8-cpu'] + permissions: # Required for workload identity auth and push the trivy results to GitHub + contents: read + id-token: write + security-events: write + steps: + + - name: Checkout + uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@main + with: + workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-optimism/providers/github-by-repos + service-account: celo-optimism-gh@devopsre.iam.gserviceaccount.com + access-token-lifetime: "60m" + docker-gcp-registries: us-west1-docker.pkg.dev + + - name: Build, push and scan the container + uses: celo-org/reusable-workflows/.github/actions/build-container@main + with: + platforms: linux/amd64 + registry: us-west1-docker.pkg.dev/devopsre/dev-images/op-ufm + tags: test + context: . + dockerfile: op-ufm/Dockerfile + push: true + trivy: false
diff --git OP/.github/workflows/pages.yml CELO/.github/workflows/pages.yml new file mode 100644 index 0000000000000000000000000000000000000000..7e26fa340725e7dec59de3af325e9db08ea063a5 --- /dev/null +++ CELO/.github/workflows/pages.yml @@ -0,0 +1,33 @@ +name: Build and publish forkdiff github-pages +permissions: + contents: write +on: + push: + branches: + - celo[0-9]+ +jobs: + deploy: + concurrency: ci-${{ github.ref }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 # make sure to fetch the old commit we diff against + + - name: Build forkdiff + uses: "docker://protolambda/forkdiff:0.1.0" + with: + args: -repo=/github/workspace -fork=/github/workspace/fork.yaml -out=/github/workspace/index.html + + - name: Build pages + run: | + mkdir -p tmp/pages + mv index.html tmp/pages/index.html + touch tmp/pages/.nojekyll + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: tmp/pages + clean: true
diff --git OP/.github/workflows/release-docker-canary.yml CELO/.github/workflows/release-docker-canary.yml index 34109152b414dce26f28f9e5d2e95890aacc51d5..2f82cbdbc7a0064f1ca1e77ac0c3fad192e70730 100644 --- OP/.github/workflows/release-docker-canary.yml +++ CELO/.github/workflows/release-docker-canary.yml @@ -16,7 +16,6 @@ runs-on: ubuntu-latest # map the step outputs to job outputs outputs: balance-mon: ${{ steps.packages.outputs.balance-mon }} - drippie-mon: ${{ steps.packages.outputs.drippie-mon }} fault-mon: ${{ steps.packages.outputs.fault-mon }} multisig-mon: ${{ steps.packages.outputs.multisig-mon }} replica-mon: ${{ steps.packages.outputs.replica-mon }} @@ -61,7 +60,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages @@ -88,7 +87,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages @@ -115,7 +114,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages @@ -123,33 +122,6 @@ target: multisig-mon push: true tags: ethereumoptimism/multisig-mon:${{ needs.canary-publish.outputs.canary-docker-tag }}   - drippie-mon: - name: Publish Drippie Monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }} - needs: canary-publish - if: needs.canary-publish.outputs.drippie-mon != '' - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} - password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }} - - - name: Build and push - uses: docker/build-push-action@v2 - with: - context: . - file: ./ops/docker/Dockerfile.packages - target: drippie-mon - push: true - tags: ethereumoptimism/drippie-mon:${{ needs.canary-publish.outputs.canary-docker-tag }} - wd-mon: name: Publish Withdrawal Monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }} needs: canary-publish @@ -169,7 +141,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages @@ -196,7 +168,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages
diff --git OP/.github/workflows/release.yml CELO/.github/workflows/release.yml index 8424d9aa7855c41be3de06529c06fa9229bdf301..d51706dc6e9e60bbc9cefd0db1c686b261211fbb 100644 --- OP/.github/workflows/release.yml +++ CELO/.github/workflows/release.yml @@ -17,7 +17,6 @@ if: github.repository == 'ethereum-optimism/optimism' # map the step outputs to job outputs outputs: balance-mon: ${{ steps.packages.outputs.balance-mon }} - drippie-mon: ${{ steps.packages.outputs.drippie-mon }} fault-mon: ${{ steps.packages.outputs.fault-mon }} multisig-mon: ${{ steps.packages.outputs.multisig-mon }} replica-mon: ${{ steps.packages.outputs.replica-mon }} @@ -98,7 +97,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages @@ -125,7 +124,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages @@ -152,7 +151,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages @@ -179,7 +178,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages @@ -187,33 +186,6 @@ target: multisig-mon push: true tags: ethereumoptimism/multisig-mon:${{ needs.release.outputs.multisig-mon }},ethereumoptimism/multisig-mon:latest   - drippie-mon: - name: Publish Drippie Monitor Version ${{ needs.release.outputs.drippie-mon }} - needs: release - if: needs.release.outputs.drippie-mon != '' - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} - password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }} - - - name: Build and push - uses: docker/build-push-action@v2 - with: - context: . - file: ./ops/docker/Dockerfile.packages - target: drippie-mon - push: true - tags: ethereumoptimism/drippie-mon:${{ needs.release.outputs.drippie-mon }},ethereumoptimism/drippie-mon:latest - replica-mon: name: Publish Replica Healthcheck Version ${{ needs.release.outputs.replica-mon }} needs: release @@ -233,7 +205,7 @@ username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }} password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}   - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./ops/docker/Dockerfile.packages
diff --git OP/.github/workflows/tag-service.yml CELO/.github/workflows/tag-service.yml index 43e581b2cdd14011049cd38dc4a619fc962b011d..80390ef689e6d6713c4f754724e2e40e1fe108d7 100644 --- OP/.github/workflows/tag-service.yml +++ CELO/.github/workflows/tag-service.yml @@ -30,7 +30,6 @@ - op-program - op-dispute-mon - op-ufm - da-server - - proxyd - op-contracts - op-conductor prerelease:
diff --git OP/CONTRIBUTING.md CELO/CONTRIBUTING.md index f27642d2d472c37ac584095948e07bdf7d673b8d..16ce4e6be1b76c11cbf3567ec25d690e132298b1 100644 --- OP/CONTRIBUTING.md +++ CELO/CONTRIBUTING.md @@ -1,66 +1,66 @@ -# Optimism monorepo contributing guide - -🎈 Thanks for your help improving the project! We are so happy to have you! - -**No contribution is too small and all contributions are valued.** - -There are plenty of ways to contribute, in particular we appreciate support in the following areas: - -- Reporting issues. For security issues see [Security policy](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md). -- Fixing and responding to existing issues. You can start off with those tagged ["good first issue"](https://github.com/ethereum-optimism/optimism/labels/D-good-first-issue) which are meant as introductory issues for external contributors. -- Improving the [community site](https://community.optimism.io/), [documentation](https://github.com/ethereum-optimism/community-hub) and [tutorials](https://github.com/ethereum-optimism/optimism-tutorial). -- Become an "Optimizer" and answer questions in the [Optimism Discord](https://discord.optimism.io). -- Get involved in the protocol design process by proposing changes or new features or write parts of the spec yourself in the [specs subdirectory](./specs/). +# Optimism Monorepo Contributing Guide   -Note that we have a [Code of Conduct](https://github.com/ethereum-optimism/.github/blob/master/CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. +## What to Contribute   -## Workflow for Pull Requests - -🚨 Before making any non-trivial change, please first open an issue describing the change to solicit feedback and guidance. This will increase the likelihood of the PR getting merged. +Welcome to the Optimism Monorepo Contributing Guide! +If you're reading this then you might be interested in contributing to the Optimism Monorepo. +Before diving into the specifics of this repository, you might be interested in taking a quick look at just a few of the ways that you can contribute. +You can:   -In general, the smaller the diff the easier it will be for us to review quickly. +- Report issues in this repository. Great bug reports are detailed and give clear instructions for how a developer can reproduce the problem. Write good bug reports and developers will love you. + - **IMPORTANT**: If you believe your report impacts the security of this repository, refer to the canonical [Security Policy](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md) document. +- Fix issues that are tagged as [`D-good-first-issue`](https://github.com/ethereum-optimism/optimism/labels/D-good-first-issue) or [`S-confirmed`](https://github.com/ethereum-optimism/optimism/labels/S-confirmed). +- Help improve the [Optimism Developer Docs](https://github.com/ethereum-optimism/docs) by reporting issues, fixing typos, or adding missing sections. +- Get involved in the protocol design process by joining discussions within the [OP Stack Specs](https://github.com/ethereum-optimism/specs/discussions) repository.   -In order to contribute, fork the appropriate branch, for non-breaking changes to production that is `develop` and for the next release that is normally `release/X.X.X` branch, see [details about our branching model](https://github.com/ethereum-optimism/optimism/blob/develop/README.md#branching-model-and-releases). +## Code of Conduct   -Additionally, if you are writing a new feature, please ensure you add appropriate test cases. +Interactions within this repository are subject to a [Code of Conduct](https://github.com/ethereum-optimism/.github/blob/master/CODE_OF_CONDUCT.md) adapted from the [Contributor Covenant](https://www.contributor-covenant.org/version/1/4/code-of-conduct/).   -Follow the [Development Quick Start](#development-quick-start) to set up your local development environment. +## Development Quick Start   -We recommend using the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format on commit messages. +### Software Dependencies   -Unless your PR is ready for immediate review and merging, please mark it as 'draft' (or simply do not open a PR yet). +| Dependency | Version | Version Check Command | +| ------------------------------------------------------------- | -------- | ------------------------ | +| [git](https://git-scm.com/) | `^2` | `git --version` | +| [go](https://go.dev/) | `^1.21` | `go version` | +| [node](https://nodejs.org/en/) | `^20` | `node --version` | +| [nvm](https://github.com/nvm-sh/nvm) | `^0.39` | `nvm --version` | +| [pnpm](https://pnpm.io/installation) | `^8` | `pnpm --version` | +| [foundry](https://github.com/foundry-rs/foundry#installation) | `^0.2.0` | `forge --version` | +| [make](https://linux.die.net/man/1/make) | `^3` | `make --version` | +| [jq](https://github.com/jqlang/jq) | `^1.6` | `jq --version` | +| [direnv](https://direnv.net) | `^2` | `direnv --version` | +| [docker](https://docs.docker.com/get-docker/) | `^24` | `docker --version` | +| [docker compose](https://docs.docker.com/compose/install/) | `^2.23` | `docker compose version` |   -Once ready for review, make sure to include a thorough PR description to help reviewers. You can read more about the guidelines for opening PRs in the [PR Guidelines](docs/handbook/pr-guidelines.md) file. +### Notes on Specific Dependencies   -**Bonus:** Add comments to the diff under the "Files Changed" tab on the PR page to clarify any sections where you think we might have questions about the approach taken. +#### `node`   -### Response time: -We aim to provide a meaningful response to all PRs and issues from external contributors within 2 business days. +Make sure to use the version of `node` specified within [`.nvmrc`](..nvmrc). +You can use [`nvm`](https://github.com/nvm-sh/nvm) to manage multiple versions of Node.js on your machine and automatically switch to the correct version when you enter this repository.   -### Rebasing +#### `foundry`   -We use the `git rebase` command to keep our commit history tidy. -Rebasing is an easy way to make sure that each PR includes a series of clean commits with descriptive commit messages -See [this tutorial](https://docs.gitlab.com/ee/topics/git/git_rebase.html) for a detailed explanation of `git rebase` and how you should use it to maintain a clean commit history. +`foundry` is updated frequently and occasionally contains breaking changes. +This repository pins a specific version of `foundry` inside of [`versions.json`](./versions.json). +Use the command `pnpm update:foundry` at the root of the monorepo to make sure that your version of `foundry` is the same as the one currently being used in CI.   -## Development Quick Start +#### `direnv`   -### Dependencies +[`direnv`](https://direnv.net) is a tool used to load environment variables from [`.envrc`](./.envrc) into your shell so you don't have to manually export variables every time you want to use them. +`direnv` only has access to files that you explicitly allow it to see. +After [installing `direnv`](https://direnv.net/docs/installation.html), you will need to **make sure that [`direnv` is hooked into your shell](https://direnv.net/docs/hook.html)**. +Make sure you've followed [the guide on the `direnv` website](https://direnv.net/docs/hook.html), then **close your terminal and reopen it** so that the changes take effect (or `source` your config file if you know how to do that).   -You'll need the following: +#### `docker compose`   -* [Git](https://git-scm.com/downloads) -* [NodeJS](https://nodejs.org/en/download/) -* [Node Version Manager](https://github.com/nvm-sh/nvm) -* [pnpm](https://pnpm.io/installation) -* [Docker](https://docs.docker.com/get-docker/) -* [Docker Compose](https://docs.docker.com/compose/install/) -* [Go](https://go.dev/dl/) -* [Foundry](https://getfoundry.sh) -* [jq](https://jqlang.github.io/jq/) -* [go-ethereum](https://github.com/ethereum/go-ethereum) +[Docker Desktop](https://docs.docker.com/get-docker/) should come with `docker compose` installed by default. +You'll have to install the `compose` plugin if you're not using Docker Desktop or you're on linux.   -### Setup +### Setting Up   Clone the repository and open it:   @@ -69,83 +69,21 @@ git clone git@github.com:ethereum-optimism/optimism.git cd optimism ```   -### Install the Correct Version of NodeJS - -Install the correct node version with [nvm](https://github.com/nvm-sh/nvm) - -```bash -nvm use -``` - -### Install node modules with pnpm - -```bash -pnpm i -``` - -### Building the TypeScript packages - -[foundry](https://github.com/foundry-rs/foundry) is used for some smart contract -development in the monorepo. It is required to build the TypeScript packages -and compile the smart contracts. Install foundry [here](https://getfoundry.sh/). - -To build all of the [TypeScript packages](./packages), run: - -```bash -pnpm clean -pnpm install -pnpm build -``` - -Packages compiled when on one branch may not be compatible with packages on a different branch. -**You should recompile all packages whenever you move from one branch to another.** -Use the above commands to recompile the packages. - -### Building the rest of the system - -If you want to run an Optimism node OR **if you want to run the integration tests**, you'll need to build the rest of the system. -Note that these environment variables significantly speed up build time. - -```bash -cd ops-bedrock -export COMPOSE_DOCKER_CLI_BUILD=1 -export DOCKER_BUILDKIT=1 -docker compose build -``` - -Source code changes can have an impact on more than one container. -**If you're unsure about which containers to rebuild, just rebuild them all**: - -```bash -cd ops-bedrock -docker compose down -docker compose build -docker compose up -``` +### Building the Monorepo   -**If a node process exits with exit code: 137** you may need to increase the default memory limit of docker containers +Make sure that you've installed all of the required [Software Dependencies](#software-dependencies) before you continue. +You will need [foundry](https://github.com/foundry-rs/foundry) to build the smart contracts found within this repository. +Refer to the note on [foundry as a dependency](#foundry) for instructions.   -Finally, **if you're running into weird problems and nothing seems to be working**, run: +Install dependencies and build all packages within the monorepo by running:   ```bash -cd optimism -pnpm clean -pnpm install -pnpm build -cd ops -docker compose down -v -docker compose build -docker compose up +make build ```   -#### Viewing docker container logs - -By default, the `docker compose up` command will show logs from all services, and that -can be hard to filter through. In order to view the logs from a specific service, you can run: - -```bash -docker compose logs --follow <service name> -``` +Packages built on one branch may not be compatible with packages on a different branch. +**You should rebuild the monorepo whenever you move from one branch to another.** +Use the above command to rebuild the monorepo.   ### Running tests   @@ -168,12 +106,14 @@ ```   #### Running unit tests (Go)   -Change directory to the package you want to run tests for. Then: +Change directory to the package you want to run tests for, then: + ```shell go test ./... ```   #### Running e2e tests (Go) + See [this document](./op-e2e/README.md)   #### Running contract static analysis @@ -209,7 +149,7 @@ [meta]: https://github.com/ethereum-optimism/optimism/labels?q=m- [difficulty]: https://github.com/ethereum-optimism/optimism/labels?q=d- [status]: https://github.com/ethereum-optimism/optimism/labels?q=s-   -#### Filtering for Work +### Filtering for Work   To find tickets available for external contribution, take a look at the https://github.com/ethereum-optimism/optimism/labels/M-community label.   @@ -220,7 +160,7 @@ Also, all labels can be seen by visiting the [labels page][labels]   [labels]: https://github.com/ethereum-optimism/optimism/labels   -#### Modifying Labels +### Modifying Labels   When altering label names or deleting labels there are a few things you must be aware of.   @@ -228,3 +168,33 @@ - This may affect the mergify bot's use of labels. See the [mergify config](.github/mergify.yml). - If the https://github.com/ethereum-optimism/labels/S-stale label is altered, the [close-stale](.github/workflows/close-stale.yml) workflow should be updated. - If the https://github.com/ethereum-optimism/labels/M-dependabot label is altered, the [dependabot config](.github/dependabot.yml) file should be adjusted. - Saved label filters for project boards will not automatically update. These should be updated if label names change. + +## Workflow for Pull Requests + +🚨 Before making any non-trivial change, please first open an issue describing the change to solicit feedback and guidance. This will increase the likelihood of the PR getting merged. + +In general, the smaller the diff the easier it will be for us to review quickly. + +In order to contribute, fork the appropriate branch, for non-breaking changes to production that is `develop` and for the next release that is normally `release/X.X.X` branch, see [details about our branching model](https://github.com/ethereum-optimism/optimism/blob/develop/README.md#branching-model-and-releases). + +Additionally, if you are writing a new feature, please ensure you add appropriate test cases. + +Follow the [Development Quick Start](#development-quick-start) to set up your local development environment. + +We recommend using the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format on commit messages. + +Unless your PR is ready for immediate review and merging, please mark it as 'draft' (or simply do not open a PR yet). + +Once ready for review, make sure to include a thorough PR description to help reviewers. You can read more about the guidelines for opening PRs in the [PR Guidelines](docs/handbook/pr-guidelines.md) file. + +**Bonus:** Add comments to the diff under the "Files Changed" tab on the PR page to clarify any sections where you think we might have questions about the approach taken. + +### Response time + +We aim to provide a meaningful response to all PRs and issues from external contributors within 2 business days. + +### Rebasing + +We use the `git rebase` command to keep our commit history tidy. +Rebasing is an easy way to make sure that each PR includes a series of clean commits with descriptive commit messages +See [this tutorial](https://docs.gitlab.com/ee/topics/git/git_rebase.html) for a detailed explanation of `git rebase` and how you should use it to maintain a clean commit history.
(new)
+145
-0
diff --git OP/fork.yaml CELO/fork.yaml new file mode 100644 index 0000000000000000000000000000000000000000..67883c918beacf31752b45d7ca03d26fbf98a154 --- /dev/null +++ CELO/fork.yaml @@ -0,0 +1,145 @@ +title: "CELO <> OP optimism forkdiff" +footer: | + Fork-diff overview of changes made in [Celo's `optimism`](https://github.com/celo-org/optimism), + a fork of [Optimism's `optimism`](https://github.com/ethereum-optimism/optimism). + +base: + name: OP + url: https://github.com/ethereum-optimism/optimism + hash: 482633768be731462c7aa9b32b8eafb74289ad2e # tracks the last rebased commit +fork: + name: CELO + url: https://github.com/celo-org/optimism + ref: HEAD +def: + title: "Celo's optimism" + description: | + This is an overview of the changes in [Celo's `optimism` implementation](https://github.com/celo-org/optimism), + a fork of [Optimism's `optimism`](https://github.com/ethereum-optimism/optimism). + + Changes are currently separated by sub-package or component. Check out the [README](https://github.com/celo-org/optimism/blob/develop/README.md) + for more details about each of these components and packages. + + sub: + - title: "packages/*" + description: "" + sub: + - title: "common-ts" + description: "" + globs: + - "packages/common-ts/*" + - title: "contracts-bedrock" + description: "" + globs: + - "packages/contracts-bedrock/*" + - "packages/contracts-bedrock/*/*" + - "packages/contracts-bedrock/*/*/*" + - title: "core-utils" + description: "" + globs: + - "packages/core-utils/*" + - title: "chain-mon" + description: "" + globs: + - "packages/chain-mon/*" + - title: "sdk" + description: "" + globs: + - "packages/sdk/*" + - title: "op-bindings" + description: "" + globs: + - "op-bindings/*" + - "op-bindings/*/*" + - title: "op-batcher" + description: "" + globs: + - "op-batcher/*" + - title: "op-bootnode" + description: "" + globs: + - "op-bootnode/*" + - title: "op-chain-ops" + description: "" + globs: + - "op-chain-ops/*" + - "op-chain-ops/*/*" + - "op-chain-ops/*/*/*" + - title: "op-challenger" + description: "" + globs: + - "op-challenger/*" + - "op-challenger/*/*" + - "op-challenger/*/*/*" + - title: "op-e2e" + description: "" + globs: + - "op-e2e/*" + - "op-e2e/*/*" + - "op-e2e/*/*/*" + - "op-e2e/*/*/*/*" + + - title: "op-exporter" + description: "" + globs: + - "op-exporter/*" + - title: "op-heartbeat" + description: "" + globs: + - "op-heartbeat/*" + - title: "op-node" + description: "" + globs: + - "op-node/*" + - "op-node/*/*" + - "op-node/*/*/*" + - title: "op-program" + description: "" + globs: + - "op-program/*" + - title: "op-proposer" + description: "" + globs: + - "op-proposer/*" + - title: "op-service" + description: "" + globs: + - "op-service/*" + - title: "op-signer" + description: "" + globs: + - "op-signer/*" + - title: "op-wheel" + description: "" + globs: + - "op-wheel/*" + - title: "ops-bedrock" + description: "" + globs: + - "ops-bedrock/*" + - title: "proxyd" + description: "" + globs: + - "proxyd/*" + - title: "specs" + description: "" + globs: + - "specs/*" + - title: "indexer" + description: "" + globs: + - "indexer/*" + - "indexer/*/*" + - "indexer/*/*/*" + +# ignored globally, does not count towards line count +ignore: + - ".circleci/*" + - "*.sum" + - "go.mod" + - "fork.yaml" + - ".github/workflows/*" + - ".changeset/*" + - ".github/*" + - "CONTRIBUTING.md" + - "pnpm-lock.yaml"
diff --git OP/go.mod CELO/go.mod index b9a89cc086551a072ce89b885c3b831bcf6c9ba5..7d2ed75cea1f9afd22e0ce3a514d8cd827b2edb9 100644 --- OP/go.mod +++ CELO/go.mod @@ -3,7 +3,6 @@ go 1.21   require ( - github.com/DataDog/zstd v1.5.5 github.com/andybalholm/brotli v1.1.0 github.com/btcsuite/btcd v0.24.0 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 @@ -12,7 +11,7 @@ github.com/consensys/gnark-crypto v0.12.1 github.com/crate-crypto/go-kzg-4844 v0.7.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 - github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240610174713-583ceb57407d + github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240614103325-d8902381f5d8 github.com/ethereum/go-ethereum v1.13.15 github.com/fsnotify/fsnotify v1.7.0 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb @@ -25,14 +24,15 @@ github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e github.com/holiman/uint256 v1.2.4 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-leveldb v0.5.0 + github.com/klauspost/compress v1.17.9 github.com/libp2p/go-libp2p v0.35.0 github.com/libp2p/go-libp2p-mplex v0.9.0 github.com/libp2p/go-libp2p-pubsub v0.11.0 github.com/libp2p/go-libp2p-testing v0.12.0 github.com/mattn/go-isatty v0.0.20 - github.com/minio/minio-go/v7 v7.0.71 + github.com/minio/minio-go/v7 v7.0.72 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.12.4 + github.com/multiformats/go-multiaddr v0.13.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/gomega v1.31.1 @@ -49,6 +49,7 @@ golang.org/x/time v0.5.0 )   require ( + github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/allegro/bigcache v1.2.1 // indirect @@ -126,7 +127,6 @@ github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c // indirect - github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -226,7 +226,7 @@ lukechampine.com/blake3 v1.2.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect )   -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101315.2-rc.1 +replace github.com/ethereum/go-ethereum => github.com/celo-org/op-geth v0.0.0-20240626110223-99afa464d86e   //replace github.com/ethereum/go-ethereum v1.13.9 => ../op-geth
diff --git OP/go.sum CELO/go.sum index 0c66f61d48ae307f5b1c18b6491a79813430011d..49a163f882ce2b2849619d633d26cd81816c22b7 100644 --- OP/go.sum +++ CELO/go.sum @@ -10,8 +10,8 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= @@ -77,6 +77,8 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/celo-org/op-geth v0.0.0-20240626110223-99afa464d86e h1:s2kq9PdLiAPY3W2okIbHol2qZRZHYBQnEFwmJ1yuwQ8= +github.com/celo-org/op-geth v0.0.0-20240626110223-99afa464d86e/go.mod h1:vObZmT4rKd8hjSblIktsJHtLX8SXbCoaIXEd42HMDB0= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -170,10 +172,8 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101315.2-rc.1 h1:9sNukA27AqReNkBEaus2kf+DLNXgkksRj+NbJHYgqxc= -github.com/ethereum-optimism/op-geth v1.101315.2-rc.1/go.mod h1:vObZmT4rKd8hjSblIktsJHtLX8SXbCoaIXEd42HMDB0= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240610174713-583ceb57407d h1:lGvrOam2IOoszfxqrpXeIFIT58/e6N1VuOLVaai9GOg= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240610174713-583ceb57407d/go.mod h1:7xh2awFQqsiZxFrHKTgEd+InVfDRrkKVUIuK8SAFHp0= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240614103325-d8902381f5d8 h1:CTeE8ZsqRwwV0z8NVazSyXgRx4aAPwtCucN/IkfYqdU= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240614103325-d8902381f5d8/go.mod h1:/S7Chw9Xo8Nx6Ranq2OMyeyG+9mFvgBYvLZk4JyTw/k= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -389,8 +389,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -480,8 +480,8 @@ github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.71 h1:No9XfOKTYi6i0GnBj+WZwD8WP5GZfL7n7GOjRqCdAjA= -github.com/minio/minio-go/v7 v7.0.71/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo= +github.com/minio/minio-go/v7 v7.0.72 h1:ZSbxs2BfJensLyHdVOgHv+pfmvxYraaUy07ER04dWnA= +github.com/minio/minio-go/v7 v7.0.72/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= @@ -506,8 +506,8 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.12.4 h1:rrKqpY9h+n80EwhhC/kkcunCZZ7URIF8yN1WEUt2Hvc= -github.com/multiformats/go-multiaddr v0.12.4/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= +github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ= +github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=